Merge pull request #2757 from emqx/introduce-proper-testing-framework

Update all the test cases and improve some modules
This commit is contained in:
Feng Lee 2019-08-06 10:14:55 +08:00 committed by GitHub
commit efa2de74a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 1034 additions and 3762 deletions

View File

@ -31,8 +31,8 @@
[{deps,
[{meck, "0.8.13"}, % hex
{bbmustache, "1.7.0"}, % hex
{emqx_ct_helpers, "1.1.3"}, % hex
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.0.1"}}}
{emqtt, {git, "https://github.com/emqx/emqtt", {tag, "v1.0.1"}}},
{emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "develop"}}}
]}
]}
]}.

View File

@ -32,9 +32,10 @@
-export([start_link/0]).
-export([ add/1
-export([ check/1
, add/1
, delete/1
, check/1
, info/1
]).
%% gen_server callbacks
@ -81,7 +82,11 @@ add(Banned) when is_record(Banned, banned) ->
-spec(delete({client_id, emqx_types:client_id()} |
{username, emqx_types:username()} |
{peername, emqx_types:peername()}) -> ok).
delete(Key) -> mnesia:dirty_delete(?BANNED_TAB, Key).
delete(Key) ->
mnesia:dirty_delete(?BANNED_TAB, Key).
info(InfoKey) ->
mnesia:table_info(?BANNED_TAB, InfoKey).
%%--------------------------------------------------------------------
%% gen_server callbacks

View File

@ -16,8 +16,11 @@
-module(emqx_inflight).
-compile(inline).
%% APIs
-export([ new/1
-export([ new/0
, new/1
, contain/2
, lookup/2
, insert/3
@ -45,9 +48,8 @@
-define(Inflight(MaxSize, Tree), {inflight, MaxSize, (Tree)}).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-spec(new() -> inflight()).
new() -> new(0).
-spec(new(non_neg_integer()) -> inflight()).
new(MaxSize) when MaxSize >= 0 ->

View File

@ -16,6 +16,8 @@
-module(emqx_json).
-compile(inline).
-export([ encode/1
, encode/2
, safe_encode/1
@ -32,7 +34,8 @@
encode(Term) ->
jsx:encode(Term).
-spec(encode(jsx:json_term(), jsx_to_json:config()) -> jsx:json_text()).
-spec(encode(jsx:json_term(), jsx_to_json:config())
-> jsx:json_text()).
encode(Term, Opts) ->
jsx:encode(Term, Opts).
@ -55,7 +58,8 @@ safe_encode(Term, Opts) ->
decode(Json) ->
jsx:decode(Json).
-spec(decode(jsx:json_text(), jsx_to_json:config()) -> jsx:json_term()).
-spec(decode(jsx:json_text(), jsx_to_json:config())
-> jsx:json_term()).
decode(Json, Opts) ->
jsx:decode(Json, Opts).

View File

@ -17,7 +17,7 @@
-module(emqx_mountpoint).
-include("emqx.hrl").
-include("logger.hrl").
-include("types.hrl").
-export([ mount/2
, unmount/2
@ -29,41 +29,46 @@
-type(mountpoint() :: binary()).
%%--------------------------------------------------------------------
%% APIs
%%--------------------------------------------------------------------
-spec(mount(maybe(mountpoint()), Any) -> Any
when Any :: emqx_types:topic()
| emqx_types:message()
| emqx_types:topic_filters()).
mount(undefined, Any) ->
Any;
mount(MountPoint, Topic) when is_binary(Topic) ->
<<MountPoint/binary, Topic/binary>>;
prefix(MountPoint, Topic);
mount(MountPoint, Msg = #message{topic = Topic}) ->
Msg#message{topic = <<MountPoint/binary, Topic/binary>>};
Msg#message{topic = prefix(MountPoint, Topic)};
mount(MountPoint, TopicFilters) when is_list(TopicFilters) ->
[{<<MountPoint/binary, Topic/binary>>, SubOpts}
|| {Topic, SubOpts} <- TopicFilters].
[{prefix(MountPoint, Topic), SubOpts} || {Topic, SubOpts} <- TopicFilters].
unmount(undefined, Msg) ->
Msg;
%% TODO: Fixme later
%% @private
-compile({inline, [prefix/2]}).
prefix(MountPoint, Topic) ->
<<MountPoint/binary, Topic/binary>>.
-spec(unmount(maybe(mountpoint()), Any) -> Any
when Any :: emqx_types:topic()
| emqx_types:message()).
unmount(undefined, Any) ->
Any;
unmount(MountPoint, Topic) when is_binary(Topic) ->
try split_binary(Topic, byte_size(MountPoint)) of
{MountPoint, Topic1} -> Topic1
catch
error:badarg-> Topic
case string:prefix(Topic, MountPoint) of
nomatch -> Topic;
Topic1 -> Topic1
end;
unmount(MountPoint, Msg = #message{topic = Topic}) ->
try split_binary(Topic, byte_size(MountPoint)) of
{MountPoint, Topic1} -> Msg#message{topic = Topic1}
catch
error:badarg->
Msg
case string:prefix(Topic, MountPoint) of
nomatch -> Msg;
Topic1 -> Msg#message{topic = Topic1}
end.
-spec(replvar(maybe(mountpoint()), map()) -> maybe(mountpoint())).
replvar(undefined, _Vars) ->
undefined;
replvar(MountPoint, #{client_id := ClientId, username := Username}) ->
lists:foldl(fun feed_var/2, MountPoint, [{<<"%c">>, ClientId}, {<<"%u">>, Username}]).
lists:foldl(fun feed_var/2, MountPoint,
[{<<"%c">>, ClientId}, {<<"%u">>, Username}]).
feed_var({<<"%c">>, ClientId}, MountPoint) ->
emqx_topic:feed_var(<<"%c">>, ClientId, MountPoint);

View File

@ -163,5 +163,7 @@ connack_error(banned) -> ?RC_BANNED;
connack_error(bad_authentication_method) -> ?RC_BAD_AUTHENTICATION_METHOD;
connack_error(_) -> ?RC_NOT_AUTHORIZED.
%%TODO: This function should be removed.
puback([]) -> ?RC_NO_MATCHING_SUBSCRIBERS;
puback(L) when is_list(L) -> ?RC_SUCCESS.

View File

@ -66,16 +66,16 @@
-type(group() :: binary()).
-type(destination() :: node() | {group(), node()}).
-type(dest() :: node() | {group(), node()}).
-define(ROUTE, emqx_route).
-define(ROUTE_TAB, emqx_route).
%%--------------------------------------------------------------------
%% Mnesia bootstrap
%%--------------------------------------------------------------------
mnesia(boot) ->
ok = ekka_mnesia:create_table(?ROUTE, [
ok = ekka_mnesia:create_table(?ROUTE_TAB, [
{type, bag},
{ram_copies, [node()]},
{record_name, route},
@ -83,7 +83,7 @@ mnesia(boot) ->
{storage_properties, [{ets, [{read_concurrency, true},
{write_concurrency, true}]}]}]);
mnesia(copy) ->
ok = ekka_mnesia:copy_table(?ROUTE).
ok = ekka_mnesia:copy_table(?ROUTE_TAB).
%%--------------------------------------------------------------------
%% Start a router
@ -102,7 +102,7 @@ start_link(Pool, Id) ->
add_route(Topic) when is_binary(Topic) ->
add_route(Topic, node()).
-spec(add_route(emqx_topic:topic(), destination()) -> ok | {error, term()}).
-spec(add_route(emqx_topic:topic(), dest()) -> ok | {error, term()}).
add_route(Topic, Dest) when is_binary(Topic) ->
call(pick(Topic), {add_route, Topic, Dest}).
@ -110,7 +110,7 @@ add_route(Topic, Dest) when is_binary(Topic) ->
do_add_route(Topic) when is_binary(Topic) ->
do_add_route(Topic, node()).
-spec(do_add_route(emqx_topic:topic(), destination()) -> ok | {error, term()}).
-spec(do_add_route(emqx_topic:topic(), dest()) -> ok | {error, term()}).
do_add_route(Topic, Dest) when is_binary(Topic) ->
Route = #route{topic = Topic, dest = Dest},
case lists:member(Route, lookup_routes(Topic)) of
@ -142,17 +142,17 @@ match_trie(Topic) ->
-spec(lookup_routes(emqx_topic:topic()) -> [emqx_types:route()]).
lookup_routes(Topic) ->
ets:lookup(?ROUTE, Topic).
ets:lookup(?ROUTE_TAB, Topic).
-spec(has_routes(emqx_topic:topic()) -> boolean()).
has_routes(Topic) when is_binary(Topic) ->
ets:member(?ROUTE, Topic).
ets:member(?ROUTE_TAB, Topic).
-spec(delete_route(emqx_topic:topic()) -> ok | {error, term()}).
delete_route(Topic) when is_binary(Topic) ->
delete_route(Topic, node()).
-spec(delete_route(emqx_topic:topic(), destination()) -> ok | {error, term()}).
-spec(delete_route(emqx_topic:topic(), dest()) -> ok | {error, term()}).
delete_route(Topic, Dest) when is_binary(Topic) ->
call(pick(Topic), {delete_route, Topic, Dest}).
@ -160,7 +160,7 @@ delete_route(Topic, Dest) when is_binary(Topic) ->
do_delete_route(Topic) when is_binary(Topic) ->
do_delete_route(Topic, node()).
-spec(do_delete_route(emqx_topic:topic(), destination()) -> ok | {error, term()}).
-spec(do_delete_route(emqx_topic:topic(), dest()) -> ok | {error, term()}).
do_delete_route(Topic, Dest) ->
Route = #route{topic = Topic, dest = Dest},
case emqx_topic:wildcard(Topic) of
@ -170,7 +170,7 @@ do_delete_route(Topic, Dest) ->
-spec(topics() -> list(emqx_topic:topic())).
topics() ->
mnesia:dirty_all_keys(?ROUTE).
mnesia:dirty_all_keys(?ROUTE_TAB).
%% @doc Print routes to a topic
-spec(print_routes(emqx_topic:topic()) -> ok).
@ -224,25 +224,25 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
insert_direct_route(Route) ->
mnesia:async_dirty(fun mnesia:write/3, [?ROUTE, Route, sticky_write]).
mnesia:async_dirty(fun mnesia:write/3, [?ROUTE_TAB, Route, sticky_write]).
insert_trie_route(Route = #route{topic = Topic}) ->
case mnesia:wread({?ROUTE, Topic}) of
case mnesia:wread({?ROUTE_TAB, Topic}) of
[] -> emqx_trie:insert(Topic);
_ -> ok
end,
mnesia:write(?ROUTE, Route, sticky_write).
mnesia:write(?ROUTE_TAB, Route, sticky_write).
delete_direct_route(Route) ->
mnesia:async_dirty(fun mnesia:delete_object/3, [?ROUTE, Route, sticky_write]).
mnesia:async_dirty(fun mnesia:delete_object/3, [?ROUTE_TAB, Route, sticky_write]).
delete_trie_route(Route = #route{topic = Topic}) ->
case mnesia:wread({?ROUTE, Topic}) of
case mnesia:wread({?ROUTE_TAB, Topic}) of
[Route] -> %% Remove route and trie
ok = mnesia:delete_object(?ROUTE, Route, sticky_write),
ok = mnesia:delete_object(?ROUTE_TAB, Route, sticky_write),
emqx_trie:delete(Topic);
[_|_] -> %% Remove route only
mnesia:delete_object(?ROUTE, Route, sticky_write);
mnesia:delete_object(?ROUTE_TAB, Route, sticky_write);
[] -> ok
end.

View File

@ -16,12 +16,21 @@
-module(emqx_tables).
-export([new/2, delete/1]).
-export([ new/1
, new/2
]).
-export([ lookup_value/2
, lookup_value/3
]).
-export([delete/1]).
%% Create an ets table.
-spec(new(atom()) -> ok).
new(Tab) ->
new(Tab, []).
%% Create a named_table ets.
-spec(new(atom(), list()) -> ok).
new(Tab, Opts) ->
@ -32,26 +41,25 @@ new(Tab, Opts) ->
Tab -> ok
end.
-spec(delete(atom()) -> ok).
%% KV lookup
-spec(lookup_value(ets:tab(), term()) -> any()).
lookup_value(Tab, Key) ->
lookup_value(Tab, Key, undefined).
-spec(lookup_value(ets:tab(), term(), any()) -> any()).
lookup_value(Tab, Key, Def) ->
try ets:lookup_element(Tab, Key, 2)
catch
error:badarg -> Def
end.
%% Delete the ets table.
-spec(delete(ets:tab()) -> ok).
delete(Tab) ->
case ets:info(Tab, name) of
undefined ->
ok;
undefined -> ok;
Tab ->
ets:delete(Tab),
ok
end.
%% KV lookup
-spec(lookup_value(atom(), term()) -> any()).
lookup_value(Tab, Key) ->
lookup_value(Tab, Key, undefined).
-spec(lookup_value(atom(), term(), any()) -> any()).
lookup_value(Tab, Key, Def) ->
try
ets:lookup_element(Tab, Key, 2)
catch
error:badarg -> Def
end.

View File

@ -21,9 +21,16 @@
, now_secs/1
, now_ms/0
, now_ms/1
, ts_from_ms/1
]).
-compile({inline,
[ seed/0
, now_secs/0
, now_secs/1
, now_ms/0
, now_ms/1
]}).
seed() ->
rand:seed(exsplus, erlang:timestamp()).
@ -39,6 +46,3 @@ now_ms() ->
now_ms({MegaSecs, Secs, MicroSecs}) ->
(MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000).
ts_from_ms(Ms) ->
{Ms div 1000000, Ms rem 1000000, 0}.

View File

@ -16,8 +16,6 @@
-module(emqx_topic).
-include("emqx_mqtt.hrl").
%% APIs
-export([ match/2
, validate/1
@ -66,7 +64,7 @@ wildcard(['+'|_]) ->
wildcard([_H|T]) ->
wildcard(T).
%% @doc Match Topic name with filter
%% @doc Match Topic name with filter.
-spec(match(Name, Filter) -> boolean() when
Name :: topic() | words(),
Filter :: topic() | words()).
@ -74,7 +72,7 @@ match(<<$$, _/binary>>, <<$+, _/binary>>) ->
false;
match(<<$$, _/binary>>, <<$#, _/binary>>) ->
false;
match(Name, Filter) when is_binary(Name) and is_binary(Filter) ->
match(Name, Filter) when is_binary(Name), is_binary(Filter) ->
match(words(Name), words(Filter));
match([], []) ->
true;
@ -101,13 +99,15 @@ validate({Type, Topic}) when Type =:= name; Type =:= filter ->
-spec(validate(name | filter, topic()) -> true).
validate(_, <<>>) ->
error(empty_topic);
validate(_, Topic) when is_binary(Topic) and (size(Topic) > ?MAX_TOPIC_LEN) ->
validate(_, Topic) when is_binary(Topic) andalso (size(Topic) > ?MAX_TOPIC_LEN) ->
error(topic_too_long);
validate(filter, Topic) when is_binary(Topic) ->
validate2(words(Topic));
validate(name, Topic) when is_binary(Topic) ->
Words = words(Topic),
validate2(Words) and (not wildcard(Words)).
validate2(Words)
andalso (not wildcard(Words))
orelse error(topic_name_error).
validate2([]) ->
true;
@ -129,7 +129,7 @@ validate3(<<C/utf8, _Rest/binary>>) when C == $#; C == $+; C == 0 ->
validate3(<<_/utf8, Rest/binary>>) ->
validate3(Rest).
%% @doc Topic to triples
%% @doc Topic to triples.
-spec(triples(topic()) -> list(triple())).
triples(Topic) when is_binary(Topic) ->
triples(words(Topic), root, []).
@ -218,13 +218,14 @@ parse(TopicFilter) when is_binary(TopicFilter) ->
parse({TopicFilter, Options}) when is_binary(TopicFilter) ->
parse(TopicFilter, Options).
-spec(parse(topic(), map()) -> {topic(), map()}).
parse(TopicFilter = <<"$queue/", _/binary>>, #{share := _Group}) ->
error({invalid_topic_filter, TopicFilter});
parse(TopicFilter = <<?SHARE, "/", _/binary>>, #{share := _Group}) ->
parse(TopicFilter = <<"$share/", _/binary>>, #{share := _Group}) ->
error({invalid_topic_filter, TopicFilter});
parse(<<"$queue/", TopicFilter/binary>>, Options) ->
parse(TopicFilter, Options#{share => <<"$queue">>});
parse(TopicFilter = <<?SHARE, "/", Rest/binary>>, Options) ->
parse(TopicFilter = <<"$share/", Rest/binary>>, Options) ->
case binary:split(Rest, <<"/">>) of
[_Any] -> error({invalid_topic_filter, TopicFilter});
[ShareName, Filter] ->

View File

@ -34,8 +34,8 @@
-export([empty/0]).
%% Mnesia tables
-define(TRIE, emqx_trie).
-define(TRIE_NODE, emqx_trie_node).
-define(TRIE_TAB, emqx_trie).
-define(TRIE_NODE_TAB, emqx_trie_node).
%%--------------------------------------------------------------------
%% Mnesia bootstrap
@ -48,13 +48,13 @@ mnesia(boot) ->
StoreProps = [{ets, [{read_concurrency, true},
{write_concurrency, true}]}],
%% Trie table
ok = ekka_mnesia:create_table(?TRIE, [
ok = ekka_mnesia:create_table(?TRIE_TAB, [
{ram_copies, [node()]},
{record_name, trie},
{attributes, record_info(fields, trie)},
{storage_properties, StoreProps}]),
%% Trie node table
ok = ekka_mnesia:create_table(?TRIE_NODE, [
ok = ekka_mnesia:create_table(?TRIE_NODE_TAB, [
{ram_copies, [node()]},
{record_name, trie_node},
{attributes, record_info(fields, trie_node)},
@ -62,9 +62,9 @@ mnesia(boot) ->
mnesia(copy) ->
%% Copy trie table
ok = ekka_mnesia:copy_table(?TRIE),
ok = ekka_mnesia:copy_table(?TRIE_TAB),
%% Copy trie_node table
ok = ekka_mnesia:copy_table(?TRIE_NODE).
ok = ekka_mnesia:copy_table(?TRIE_NODE_TAB).
%%--------------------------------------------------------------------
%% Trie APIs
@ -73,7 +73,7 @@ mnesia(copy) ->
%% @doc Insert a topic filter into the trie.
-spec(insert(emqx_topic:topic()) -> ok).
insert(Topic) when is_binary(Topic) ->
case mnesia:wread({?TRIE_NODE, Topic}) of
case mnesia:wread({?TRIE_NODE_TAB, Topic}) of
[#trie_node{topic = Topic}] ->
ok;
[TrieNode = #trie_node{topic = undefined}] ->
@ -94,14 +94,14 @@ match(Topic) when is_binary(Topic) ->
%% @doc Lookup a trie node.
-spec(lookup(NodeId :: binary()) -> [#trie_node{}]).
lookup(NodeId) ->
mnesia:read(?TRIE_NODE, NodeId).
mnesia:read(?TRIE_NODE_TAB, NodeId).
%% @doc Delete a topic filter from the trie.
-spec(delete(emqx_topic:topic()) -> ok).
delete(Topic) when is_binary(Topic) ->
case mnesia:wread({?TRIE_NODE, Topic}) of
case mnesia:wread({?TRIE_NODE_TAB, Topic}) of
[#trie_node{edge_count = 0}] ->
ok = mnesia:delete({?TRIE_NODE, Topic}),
ok = mnesia:delete({?TRIE_NODE_TAB, Topic}),
delete_path(lists:reverse(emqx_topic:triples(Topic)));
[TrieNode] ->
write_trie_node(TrieNode#trie_node{topic = undefined});
@ -111,7 +111,7 @@ delete(Topic) when is_binary(Topic) ->
%% @doc Is the trie empty?
-spec(empty() -> boolean()).
empty() ->
ets:info(?TRIE, size) == 0.
ets:info(?TRIE_TAB, size) == 0.
%%--------------------------------------------------------------------
%% Internal functions
@ -121,9 +121,9 @@ empty() ->
%% @doc Add a path to the trie.
add_path({Node, Word, Child}) ->
Edge = #trie_edge{node_id = Node, word = Word},
case mnesia:wread({?TRIE_NODE, Node}) of
case mnesia:wread({?TRIE_NODE_TAB, Node}) of
[TrieNode = #trie_node{edge_count = Count}] ->
case mnesia:wread({?TRIE, Edge}) of
case mnesia:wread({?TRIE_TAB, Edge}) of
[] ->
ok = write_trie_node(TrieNode#trie_node{edge_count = Count + 1}),
write_trie(#trie{edge = Edge, node_id = Child});
@ -143,11 +143,11 @@ match_node(NodeId, Words) ->
match_node(NodeId, Words, []).
match_node(NodeId, [], ResAcc) ->
mnesia:read(?TRIE_NODE, NodeId) ++ 'match_#'(NodeId, ResAcc);
mnesia:read(?TRIE_NODE_TAB, NodeId) ++ 'match_#'(NodeId, ResAcc);
match_node(NodeId, [W|Words], ResAcc) ->
lists:foldl(fun(WArg, Acc) ->
case mnesia:read(?TRIE, #trie_edge{node_id = NodeId, word = WArg}) of
case mnesia:read(?TRIE_TAB, #trie_edge{node_id = NodeId, word = WArg}) of
[#trie{node_id = ChildId}] -> match_node(ChildId, Words, Acc);
[] -> Acc
end
@ -156,9 +156,9 @@ match_node(NodeId, [W|Words], ResAcc) ->
%% @private
%% @doc Match node with '#'.
'match_#'(NodeId, ResAcc) ->
case mnesia:read(?TRIE, #trie_edge{node_id = NodeId, word = '#'}) of
case mnesia:read(?TRIE_TAB, #trie_edge{node_id = NodeId, word = '#'}) of
[#trie{node_id = ChildId}] ->
mnesia:read(?TRIE_NODE, ChildId) ++ ResAcc;
mnesia:read(?TRIE_NODE_TAB, ChildId) ++ ResAcc;
[] -> ResAcc
end.
@ -167,10 +167,10 @@ match_node(NodeId, [W|Words], ResAcc) ->
delete_path([]) ->
ok;
delete_path([{NodeId, Word, _} | RestPath]) ->
ok = mnesia:delete({?TRIE, #trie_edge{node_id = NodeId, word = Word}}),
case mnesia:wread({?TRIE_NODE, NodeId}) of
ok = mnesia:delete({?TRIE_TAB, #trie_edge{node_id = NodeId, word = Word}}),
case mnesia:wread({?TRIE_NODE_TAB, NodeId}) of
[#trie_node{edge_count = 1, topic = undefined}] ->
ok = mnesia:delete({?TRIE_NODE, NodeId}),
ok = mnesia:delete({?TRIE_NODE_TAB, NodeId}),
delete_path(RestPath);
[TrieNode = #trie_node{edge_count = 1, topic = _}] ->
write_trie_node(TrieNode#trie_node{edge_count = 0});
@ -182,9 +182,9 @@ delete_path([{NodeId, Word, _} | RestPath]) ->
%% @private
write_trie(Trie) ->
mnesia:write(?TRIE, Trie, write).
mnesia:write(?TRIE_TAB, Trie, write).
%% @private
write_trie_node(TrieNode) ->
mnesia:write(?TRIE_NODE, TrieNode, write).
mnesia:write(?TRIE_NODE_TAB, TrieNode, write).

View File

@ -1,206 +0,0 @@
%% Copyright (c) 2013-2019 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(emqx_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-define(APP, emqx).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include("emqx_mqtt.hrl").
-record(ssl_socket, {tcp, ssl}).
-define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"admin">>,
password = <<"public">>})).
-define(CLIENT2, ?CONNECT_PACKET(#mqtt_packet_connect{
username = <<"admin">>,
clean_start = false,
password = <<"public">>})).
-define(CLIENT3, ?CONNECT_PACKET(#mqtt_packet_connect{
username = <<"admin">>,
proto_ver = ?MQTT_PROTO_V5,
clean_start = false,
password = <<"public">>,
will_props = #{'Will-Delay-Interval' => 2}})).
-define(SUBCODE, [0]).
-define(PACKETID, 1).
-define(PUBQOS, 1).
-define(SUBPACKET, ?SUBSCRIBE_PACKET(?PACKETID, [{<<"sub/topic">>, ?DEFAULT_SUBOPTS}])).
-define(PUBPACKET, ?PUBLISH_PACKET(?PUBQOS, <<"sub/topic">>, ?PACKETID, <<"publish">>)).
-define(PAYLOAD, [{type,"dsmSimulationData"},
{id, 9999},
{status, "running"},
{soc, 1536702170},
{fracsec, 451000},
{data, lists:seq(1, 20480)}]).
-define(BIG_PUBPACKET, ?PUBLISH_PACKET(?PUBQOS, <<"sub/topic">>, ?PACKETID, emqx_json:encode(?PAYLOAD))).
all() ->
[{group, connect},
{group, publish}].
groups() ->
[{connect, [non_parallel_tests],
[mqtt_connect,
mqtt_connect_with_tcp,
mqtt_connect_with_will_props,
mqtt_connect_with_ssl_oneway,
mqtt_connect_with_ssl_twoway,
mqtt_connect_with_ws]},
{publish, [non_parallel_tests],
[packet_size]}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
%%--------------------------------------------------------------------
%% Protocol Test
%%--------------------------------------------------------------------
mqtt_connect(_) ->
%% Issue #599
%% Empty clientId and clean_session = false
?assertEqual(<<32,2,0,2>>, connect_broker_(<<16,12,0,4,77,81,84,84,4,0,0,90,0,0>>, 4)),
%% Empty clientId and clean_session = true
?assertEqual(<<32,2,0,0>>, connect_broker_(<<16,12,0,4,77,81,84,84,4,2,0,90,0,0>>, 4)).
connect_broker_(Packet, RecvSize) ->
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
emqx_client_sock:send(Sock, Packet),
{ok, Data} = gen_tcp:recv(Sock, RecvSize, 3000),
emqx_client_sock:close(Sock),
Data.
mqtt_connect_with_tcp(_) ->
%% Issue #599
%% Empty clientId and clean_session = false
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
Packet = raw_send_serialize(?CLIENT2),
emqx_client_sock:send(Sock, Packet),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?CONNACK_INVALID_ID), <<>>, _} = raw_recv_pase(Data),
emqx_client_sock:close(Sock).
mqtt_connect_with_will_props(_) ->
%% Issue #599
%% Empty clientId and clean_session = false
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
Packet = raw_send_serialize(?CLIENT3),
emqx_client_sock:send(Sock, Packet),
emqx_client_sock:close(Sock).
mqtt_connect_with_ssl_oneway(_) ->
emqx:shutdown(),
emqx_ct_helpers:change_emqx_opts(ssl_oneway),
emqx:start(),
ClientSsl = emqx_ct_helpers:client_ssl(),
{ok, #ssl_socket{tcp = _Sock1, ssl = SslSock} = Sock}
= emqx_client_sock:connect("127.0.0.1", 8883, [{ssl_opts, ClientSsl}], 3000),
Packet = raw_send_serialize(?CLIENT),
emqx_client_sock:setopts(Sock, [{active, once}]),
emqx_client_sock:send(Sock, Packet),
?assert(
receive {ssl, _, ConAck}->
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_pase(ConAck), true
after 1000 ->
false
end),
ssl:close(SslSock).
mqtt_connect_with_ssl_twoway(_Config) ->
emqx:shutdown(),
emqx_ct_helpers:change_emqx_opts(ssl_twoway),
emqx:start(),
ClientSsl = emqx_ct_helpers:client_ssl_twoway(),
{ok, #ssl_socket{tcp = _Sock1, ssl = SslSock} = Sock}
= emqx_client_sock:connect("127.0.0.1", 8883, [{ssl_opts, ClientSsl}], 3000),
Packet = raw_send_serialize(?CLIENT),
emqx_client_sock:setopts(Sock, [{active, once}]),
emqx_client_sock:send(Sock, Packet),
timer:sleep(500),
?assert(
receive {ssl, _, Data}->
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_pase(Data), true
after 1000 ->
false
end),
ssl:close(SslSock),
emqx_client_sock:close(Sock).
mqtt_connect_with_ws(_Config) ->
WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()),
{ok, _} = rfc6455_client:open(WS),
%% Connect Packet
Packet = raw_send_serialize(?CLIENT),
ok = rfc6455_client:send_binary(WS, Packet),
{binary, CONACK} = rfc6455_client:recv(WS),
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_pase(CONACK),
%% Sub Packet
SubPacket = raw_send_serialize(?SUBPACKET),
rfc6455_client:send_binary(WS, SubPacket),
{binary, SubAck} = rfc6455_client:recv(WS),
{ok, ?SUBACK_PACKET(?PACKETID, ?SUBCODE), <<>>, _} = raw_recv_pase(SubAck),
%% Pub Packet QoS 1
PubPacket = raw_send_serialize(?PUBPACKET),
rfc6455_client:send_binary(WS, PubPacket),
{binary, PubAck} = rfc6455_client:recv(WS),
{ok, ?PUBACK_PACKET(?PACKETID), <<>>, _} = raw_recv_pase(PubAck),
{close, _} = rfc6455_client:close(WS),
ok.
%%issue 1811
packet_size(_Config) ->
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
Packet = raw_send_serialize(?CLIENT),
emqx_client_sock:send(Sock, Packet),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_pase(Data),
%% Pub Packet QoS 1
PubPacket = raw_send_serialize(?BIG_PUBPACKET),
emqx_client_sock:send(Sock, PubPacket),
{ok, Data1} = gen_tcp:recv(Sock, 0),
{ok, ?PUBACK_PACKET(?PACKETID), <<>>, _} = raw_recv_pase(Data1),
emqx_client_sock:close(Sock).
raw_send_serialize(Packet) ->
emqx_frame:serialize(Packet).
raw_recv_pase(Bin) ->
emqx_frame:parse(Bin).

View File

@ -1,29 +0,0 @@
%%--------------------------------------------------------------------
%%
%% [ACL](https://github.com/emqtt/emqttd/wiki/ACL)
%%
%% -type who() :: all | binary() |
%% {ipaddr, esockd_access:cidr()} |
%% {client, binary()} |
%% {user, binary()}.
%%
%% -type access() :: subscribe | publish | pubsub.
%%
%% -type topic() :: binary().
%%
%% -type rule() :: {allow, all} |
%% {allow, who(), access(), list(topic())} |
%% {deny, all} |
%% {deny, who(), access(), list(topic())}.
%%
%%--------------------------------------------------------------------
{allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}.
{allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}.
{deny, all, subscribe, ["$SYS/#", {eq, "#"}]}.
{allow, all}.

View File

@ -21,41 +21,46 @@
-include("emqx.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(AC, emqx_access_control).
-define(CACHE, emqx_acl_cache).
-import(emqx_access_rule, [compile/1, match/3]).
-import(emqx_access_rule,
[ compile/1
, match/3
]).
all() ->
[{group, access_control},
{group, acl_cache},
{group, access_control_cache_mode},
{group, access_rule}].
{group, access_rule}
].
groups() ->
[{access_control, [sequence],
[reload_acl,
check_acl_1,
check_acl_2]},
{access_control_cache_mode, [],
[acl_cache_basic,
acl_cache_expiry,
acl_cache_cleanup,
acl_cache_full]},
{acl_cache, [],
[put_get_del_cache,
cache_update,
cache_expiry,
cache_replacement,
cache_cleanup,
cache_auto_emtpy,
cache_auto_cleanup]},
{access_rule, [],
[compile_rule,
match_rule]}].
[t_reload_acl,
t_check_acl_1,
t_check_acl_2]},
{access_control_cache_mode, [sequence],
[t_acl_cache_basic,
t_acl_cache_expiry,
t_acl_cache_cleanup,
t_acl_cache_full]},
{acl_cache, [sequence],
[t_put_get_del_cache,
t_cache_update,
t_cache_expiry,
t_cache_replacement,
t_cache_cleanup,
t_cache_auto_emtpy,
t_cache_auto_cleanup]},
{access_rule, [parallel],
[t_compile_rule,
t_match_rule]
}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -102,62 +107,76 @@ end_per_group(_Group, Config) ->
%% emqx_access_control
%%--------------------------------------------------------------------
reload_acl(_) ->
t_reload_acl(_) ->
ok = ?AC:reload_acl().
check_acl_1(_) ->
SelfUser = #{client_id => <<"client1">>, username => <<"testuser">>, zone => external},
allow = ?AC:check_acl(SelfUser, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1">>),
deny = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1/x/y">>),
allow = ?AC:check_acl(SelfUser, publish, <<"users/testuser/1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"a/b/c">>).
check_acl_2(_) ->
SelfUser = #{client_id => <<"client2">>, username => <<"xyz">>, zone => external},
deny = ?AC:check_acl(SelfUser, subscribe, <<"a/b/c">>).
t_check_acl_1(_) ->
Client = #{zone => external,
client_id => <<"client1">>,
username => <<"testuser">>
},
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
deny = ?AC:check_acl(Client, subscribe, <<"clients/client1/x/y">>),
allow = ?AC:check_acl(Client, publish, <<"users/testuser/1">>),
allow = ?AC:check_acl(Client, subscribe, <<"a/b/c">>).
acl_cache_basic(_) ->
SelfUser = #{client_id => <<"client1">>, username => <<"testuser">>, zone => external},
t_check_acl_2(_) ->
Client = #{zone => external,
client_id => <<"client2">>,
username => <<"xyz">>
},
deny = ?AC:check_acl(Client, subscribe, <<"a/b/c">>).
t_acl_cache_basic(_) ->
Client = #{zone => external,
client_id => <<"client1">>,
username => <<"testuser">>
},
not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
not_found = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1">>),
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
ok.
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>).
acl_cache_expiry(_) ->
t_acl_cache_expiry(_) ->
application:set_env(emqx, acl_cache_ttl, 100),
SelfUser = #{client_id => <<"client1">>, username => <<"testuser">>, zone => external},
allow = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1">>),
Client = #{zone => external,
client_id => <<"client1">>,
username => <<"testuser">>
},
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
ct:sleep(150),
not_found = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
ok.
not_found = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>).
acl_cache_full(_) ->
t_acl_cache_full(_) ->
application:set_env(emqx, acl_cache_max_size, 1),
SelfUser = #{client_id => <<"client1">>, username => <<"testuser">>, zone => external},
allow = ?AC:check_acl(SelfUser, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1">>),
Client = #{zone => external,
client_id => <<"client1">>,
username => <<"testuser">>
},
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
%% the older ones (the <<"users/testuser/1">>) will be evicted first
not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
ok.
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>).
acl_cache_cleanup(_) ->
t_acl_cache_cleanup(_) ->
%% The acl cache will try to evict memory, if the size is full and the newest
%% cache entry is expired
application:set_env(emqx, acl_cache_ttl, 100),
application:set_env(emqx, acl_cache_max_size, 2),
SelfUser = #{client_id => <<"client1">>, username => <<"testuser">>, zone => external},
allow = ?AC:check_acl(SelfUser, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(SelfUser, subscribe, <<"clients/client1">>),
Client = #{zone => external,
client_id => <<"client1">>,
username => <<"testuser">>
},
allow = ?AC:check_acl(Client, subscribe, <<"users/testuser/1">>),
allow = ?AC:check_acl(Client, subscribe, <<"clients/client1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
allow = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
@ -166,14 +185,13 @@ acl_cache_cleanup(_) ->
%% now the cache is full and the newest one - "clients/client1"
%% should be expired, so we'll empty the cache before putting
%% the next cache entry
deny = ?AC:check_acl(SelfUser, subscribe, <<"#">>),
deny = ?AC:check_acl(Client, subscribe, <<"#">>),
not_found = ?CACHE:get_acl_cache(subscribe, <<"users/testuser/1">>),
not_found = ?CACHE:get_acl_cache(subscribe, <<"clients/client1">>),
deny = ?CACHE:get_acl_cache(subscribe, <<"#">>),
ok.
deny = ?CACHE:get_acl_cache(subscribe, <<"#">>).
put_get_del_cache(_) ->
t_put_get_del_cache(_) ->
application:set_env(emqx, acl_cache_ttl, 300000),
application:set_env(emqx, acl_cache_max_size, 30),
@ -188,7 +206,7 @@ put_get_del_cache(_) ->
2 = ?CACHE:get_cache_size(),
?assertEqual(?CACHE:cache_k(subscribe, <<"b">>), ?CACHE:get_newest_key()).
cache_expiry(_) ->
t_cache_expiry(_) ->
application:set_env(emqx, acl_cache_ttl, 100),
application:set_env(emqx, acl_cache_max_size, 30),
ok = ?CACHE:put_acl_cache(subscribe, <<"a">>, allow),
@ -203,7 +221,7 @@ cache_expiry(_) ->
ct:sleep(150),
not_found = ?CACHE:get_acl_cache(subscribe, <<"a">>).
cache_update(_) ->
t_cache_update(_) ->
application:set_env(emqx, acl_cache_ttl, 300000),
application:set_env(emqx, acl_cache_max_size, 30),
[] = ?CACHE:dump_acl_cache(),
@ -222,7 +240,7 @@ cache_update(_) ->
?assertEqual(?CACHE:cache_k(publish, <<"b">>), ?CACHE:get_newest_key()),
?assertEqual(?CACHE:cache_k(subscribe, <<"a">>), ?CACHE:get_oldest_key()).
cache_replacement(_) ->
t_cache_replacement(_) ->
application:set_env(emqx, acl_cache_ttl, 300000),
application:set_env(emqx, acl_cache_max_size, 3),
ok = ?CACHE:put_acl_cache(subscribe, <<"a">>, allow),
@ -248,7 +266,7 @@ cache_replacement(_) ->
not_found = ?CACHE:get_acl_cache(publish, <<"b">>),
allow = ?CACHE:get_acl_cache(publish, <<"c">>).
cache_cleanup(_) ->
t_cache_cleanup(_) ->
application:set_env(emqx, acl_cache_ttl, 100),
application:set_env(emqx, acl_cache_max_size, 30),
ok = ?CACHE:put_acl_cache(subscribe, <<"a">>, allow),
@ -261,7 +279,7 @@ cache_cleanup(_) ->
?assertEqual(?CACHE:cache_k(publish, <<"c">>), ?CACHE:get_oldest_key()),
1 = ?CACHE:get_cache_size().
cache_auto_emtpy(_) ->
t_cache_auto_emtpy(_) ->
%% verify cache is emptied when cache full and even the newest
%% one is expired.
application:set_env(emqx, acl_cache_ttl, 100),
@ -275,7 +293,7 @@ cache_auto_emtpy(_) ->
ok = ?CACHE:put_acl_cache(subscribe, <<"d">>, deny),
1 = ?CACHE:get_cache_size().
cache_auto_cleanup(_) ->
t_cache_auto_cleanup(_) ->
%% verify we'll cleanup expired entries when we got a exipired acl
%% from cache.
application:set_env(emqx, acl_cache_ttl, 100),
@ -299,7 +317,7 @@ cache_auto_cleanup(_) ->
%% emqx_access_rule
%%--------------------------------------------------------------------
compile_rule(_) ->
t_compile_rule(_) ->
{allow, {'and', [{ipaddr, {{127,0,0,1}, {127,0,0,1}, 32}},
{user, <<"user">>}]}, subscribe, [ [<<"$SYS">>, '#'], ['#'] ]} =
compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"user">>}]}, subscribe, ["$SYS/#", "#"]}),
@ -324,23 +342,36 @@ compile_rule(_) ->
{allow, all} = compile({allow, all}),
{deny, all} = compile({deny, all}).
match_rule(_) ->
User = #{client_id => <<"testClient">>, username => <<"TestUser">>, peername => {{127,0,0,1}, 2948}, zone => external},
User2 = #{client_id => <<"testClient">>, username => <<"TestUser">>, peername => {{192,168,0,10}, 3028}, zone => external},
{matched, allow} = match(User, <<"Test/Topic">>, {allow, all}),
{matched, deny} = match(User, <<"Test/Topic">>, {deny, all}),
{matched, allow} = match(User, <<"Test/Topic">>, compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})),
{matched, allow} = match(User2, <<"Test/Topic">>, compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]})),
{matched, allow} = match(User, <<"d/e/f/x">>, compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]})),
nomatch = match(User, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})),
{matched, allow} = match(User, <<"testTopics/testClient">>, compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})),
{matched, allow} = match(User, <<"clients/testClient">>, compile({allow, all, pubsub, ["clients/%c"]})),
{matched, allow} = match(#{username => <<"user2">>}, <<"users/user2/abc/def">>, compile({allow, all, subscribe, ["users/%u/#"]})),
{matched, deny} = match(User, <<"d/e/f">>, compile({deny, all, subscribe, ["$SYS/#", "#"]})),
t_match_rule(_) ->
Client1 = #{zone => external,
client_id => <<"testClient">>,
username => <<"TestUser">>,
peername => {{127,0,0,1}, 2948}
},
Client2 = #{zone => external,
client_id => <<"testClient">>,
username => <<"TestUser">>,
peername => {{192,168,0,10}, 3028}
},
{matched, allow} = match(Client1, <<"Test/Topic">>, {allow, all}),
{matched, deny} = match(Client1, <<"Test/Topic">>, {deny, all}),
{matched, allow} = match(Client1, <<"Test/Topic">>,
compile({allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]})),
{matched, allow} = match(Client2, <<"Test/Topic">>,
compile({allow, {ipaddr, "192.168.0.1/24"}, subscribe, ["$SYS/#", "#"]})),
{matched, allow} = match(Client1, <<"d/e/f/x">>,
compile({allow, {user, "TestUser"}, subscribe, ["a/b/c", "d/e/f/#"]})),
nomatch = match(Client1, <<"d/e/f/x">>, compile({allow, {user, "admin"}, pubsub, ["d/e/f/#"]})),
{matched, allow} = match(Client1, <<"testTopics/testClient">>,
compile({allow, {client, "testClient"}, publish, ["testTopics/testClient"]})),
{matched, allow} = match(Client1, <<"clients/testClient">>, compile({allow, all, pubsub, ["clients/%c"]})),
{matched, allow} = match(#{username => <<"user2">>}, <<"users/user2/abc/def">>,
compile({allow, all, subscribe, ["users/%u/#"]})),
{matched, deny} = match(Client1, <<"d/e/f">>, compile({deny, all, subscribe, ["$SYS/#", "#"]})),
Rule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, <<"Topic">>}),
nomatch = match(User, <<"Topic">>, Rule),
nomatch = match(Client1, <<"Topic">>, Rule),
AndRule = compile({allow, {'and', [{ipaddr, "127.0.0.1"}, {user, <<"TestUser">>}]}, publish, <<"Topic">>}),
{matched, allow} = match(User, <<"Topic">>, AndRule),
{matched, allow} = match(Client1, <<"Topic">>, AndRule),
OrRule = compile({allow, {'or', [{ipaddr, "127.0.0.1"}, {user, <<"WrongUser">>}]}, publish, ["Topic"]}),
{matched, allow} = match(User, <<"Topic">>, OrRule).
{matched, allow} = match(Client1, <<"Topic">>, OrRule).

View File

@ -17,7 +17,11 @@
-module(emqx_acl_test_mod).
%% ACL callbacks
-export([init/1, check_acl/2, reload_acl/1, description/0]).
-export([ init/1
, check_acl/2
, reload_acl/1
, description/0
]).
init(AclOpts) ->
{ok, AclOpts}.

View File

@ -1,110 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_alarm_handler_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include("emqx_mqtt.hrl").
-include("emqx.hrl").
all() -> [t_alarm_handler].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([], fun set_special_configs/1),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
set_special_configs(emqx) ->
application:set_env(emqx, acl_file, emqx_ct_helpers:deps_path(emqx, "test/emqx_access_SUITE_data/acl_deny_action.conf"));
set_special_configs(_App) ->
ok.
with_connection(DoFun) ->
{ok, Sock} = emqx_client_sock:connect({127, 0, 0, 1}, 1883,
[binary, {packet, raw}, {active, false}],
3000),
try
DoFun(Sock)
after
emqx_client_sock:close(Sock)
end.
t_alarm_handler(_) ->
with_connection(
fun(Sock) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5}),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5),
Topic1 = emqx_topic:systop(<<"alarms/alert">>),
Topic2 = emqx_topic:systop(<<"alarms/clear">>),
SubOpts = #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0},
emqx_client_sock:send(Sock,
raw_send_serialize(
?SUBSCRIBE_PACKET(
1,
[{Topic1, SubOpts},
{Topic2, SubOpts}]),
#{version => ?MQTT_PROTO_V5})),
{ok, Data2} = gen_tcp:recv(Sock, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2, 2]), <<>>, _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5),
alarm_handler:set_alarm({alarm_for_test, #alarm{id = alarm_for_test,
severity = error,
title="alarm title",
summary="alarm summary"}}),
{ok, Data3} = gen_tcp:recv(Sock, 0),
{ok, ?PUBLISH_PACKET(?QOS_0, Topic1, _, _), <<>>, _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5),
?assertEqual(true, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms())),
alarm_handler:clear_alarm(alarm_for_test),
{ok, Data4} = gen_tcp:recv(Sock, 0),
{ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4, ?MQTT_PROTO_V5),
?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms()))
end).
raw_send_serialize(Packet) ->
emqx_frame:serialize(Packet).
raw_send_serialize(Packet, Opts) ->
emqx_frame:serialize(Packet, Opts).
raw_recv_parse(Bin, ProtoVer) ->
emqx_frame:parse(Bin, {none, #{max_size => ?MAX_PACKET_SIZE,
version => ProtoVer}}).

View File

@ -1,29 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_auth_anonymous_test_mod).
%% ACL callbacks
-export([init/1, check/3, description/0]).
init(AclOpts) ->
{ok, AclOpts}.
check(_Client, _Password, _Opts) ->
allow.
description() ->
"Test emqx_auth_anonymous Mod".

View File

@ -1,30 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_auth_dashboard).
%% Auth callbacks
-export([init/1, check/3, description/0]).
init(Opts) ->
{ok, Opts}.
check(_Client, _Password, _Opts) ->
allow.
description() ->
"Test Auth Mod".

View File

@ -20,35 +20,63 @@
-compile(nowarn_export_all).
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [t_banned_all].
all() -> emqx_ct:all(?MODULE).
t_banned_all(_) ->
emqx_ct_helpers:start_apps([]),
emqx_banned:start_link(),
TimeNow = erlang:system_time(second),
init_per_suite(Config) ->
application:load(emqx),
ok = ekka:start(),
Config.
end_per_suite(_Config) ->
ekka:stop(),
ekka_mnesia:ensure_stopped(),
ekka_mnesia:delete_schema().
t_add_delete(_) ->
Banned = #banned{who = {client_id, <<"TestClient">>},
reason = <<"test">>,
by = <<"banned suite">>,
desc = <<"test">>,
until = TimeNow + 1},
until = erlang:system_time(second) + 1000
},
ok = emqx_banned:add(Banned),
% here is not expire banned test because its check interval is greater than 5 mins, but its effect has been confirmed
?assert(emqx_banned:check(#{client_id => <<"TestClient">>,
username => undefined,
peername => {undefined, undefined}})),
timer:sleep(2500),
?assertNot(emqx_banned:check(#{client_id => <<"TestClient">>,
username => undefined,
peername => {undefined, undefined}})),
ok = emqx_banned:add(Banned),
?assert(emqx_banned:check(#{client_id => <<"TestClient">>,
username => undefined,
peername => {undefined, undefined}})),
emqx_banned:delete({client_id, <<"TestClient">>}),
?assertNot(emqx_banned:check(#{client_id => <<"TestClient">>,
username => undefined,
peername => {undefined, undefined}})),
emqx_ct_helpers:stop_apps([]).
?assertEqual(1, emqx_banned:info(size)),
ok = emqx_banned:delete({client_id, <<"TestClient">>}),
?assertEqual(0, emqx_banned:info(size)).
t_check(_) ->
ok = emqx_banned:add(#banned{who = {client_id, <<"BannedClient">>}}),
ok = emqx_banned:add(#banned{who = {username, <<"BannedUser">>}}),
ok = emqx_banned:add(#banned{who = {ipaddr, {192,168,0,1}}}),
?assertEqual(3, emqx_banned:info(size)),
Client1 = #{client_id => <<"BannedClient">>,
username => <<"user">>,
peername => {{127,0,0,1}, 5000}
},
Client2 = #{client_id => <<"client">>,
username => <<"BannedUser">>,
peername => {{127,0,0,1}, 5000}
},
Client3 = #{client_id => <<"client">>,
username => <<"user">>,
peername => {{192,168,0,1}, 5000}
},
Client4 = #{client_id => <<"client">>,
username => <<"user">>,
peername => {{127,0,0,1}, 5000}
},
?assert(emqx_banned:check(Client1)),
?assert(emqx_banned:check(Client2)),
?assert(emqx_banned:check(Client3)),
?assertNot(emqx_banned:check(Client4)),
ok = emqx_banned:delete({client_id, <<"BannedClient">>}),
ok = emqx_banned:delete({username, <<"BannedUser">>}),
ok = emqx_banned:delete({ipaddr, {192,168,0,1}}),
?assertNot(emqx_banned:check(Client1)),
?assertNot(emqx_banned:check(Client2)),
?assertNot(emqx_banned:check(Client3)),
?assertNot(emqx_banned:check(Client4)),
?assertEqual(0, emqx_banned:info(size)).

View File

@ -21,11 +21,13 @@
-include_lib("eunit/include/eunit.hrl").
all() ->
[batch_full_commit, batch_linger_commit].
all() -> emqx_ct:all(?MODULE).
batch_full_commit(_) ->
B0 = emqx_batch:init(#{batch_size => 3, linger_ms => 2000, commit_fun => fun(_) -> ok end}),
t_batch_full_commit(_) ->
B0 = emqx_batch:init(#{batch_size => 3,
linger_ms => 2000,
commit_fun => fun(_) -> ok end
}),
B3 = lists:foldl(fun(E, B) -> emqx_batch:push(E, B) end, B0, [a, b, c]),
?assertEqual(3, emqx_batch:size(B3)),
?assertEqual([a, b, c], emqx_batch:items(B3)),
@ -34,9 +36,12 @@ batch_full_commit(_) ->
?assertEqual(0, emqx_batch:size(B4)),
?assertEqual([], emqx_batch:items(B4)).
batch_linger_commit(_) ->
t_batch_linger_commit(_) ->
CommitFun = fun(Q) -> ?assertEqual(3, length(Q)) end,
B0 = emqx_batch:init(#{batch_size => 3, linger_ms => 500, commit_fun => CommitFun}),
B0 = emqx_batch:init(#{batch_size => 3,
linger_ms => 500,
commit_fun => CommitFun
}),
B3 = lists:foldl(fun(E, B) -> emqx_batch:push(E, B) end, B0, [a, b, c]),
?assertEqual(3, emqx_batch:size(B3)),
?assertEqual([a, b, c], emqx_batch:items(B3)),

View File

@ -29,19 +29,24 @@
all() ->
[{group, pubsub},
{group, session},
{group, metrics},
{group, stats}].
groups() ->
[{pubsub, [sequence], [subscribe_unsubscribe,
publish, pubsub,
t_shared_subscribe,
dispatch_with_no_sub,
'pubsub#', 'pubsub+']},
{session, [sequence], [start_session]},
{metrics, [sequence], [inc_dec_metric]},
{stats, [sequence], [set_get_stat]}].
[{pubsub, [sequence],
[t_sub_unsub,
t_publish,
t_pubsub,
t_shared_subscribe,
t_dispatch_with_no_sub,
't_pubsub#',
't_pubsub+'
]},
{metrics, [sequence],
[inc_dec_metric]},
{stats, [sequence],
[set_get_stat]
}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -54,47 +59,48 @@ end_per_suite(_Config) ->
%% PubSub Test
%%--------------------------------------------------------------------
subscribe_unsubscribe(_) ->
ok = emqx:subscribe(<<"topic">>, <<"clientId">>),
ok = emqx:subscribe(<<"topic/1">>, <<"clientId">>, #{ qos => 1 }),
ok = emqx:subscribe(<<"topic/2">>, <<"clientId">>, #{ qos => 2 }),
true = emqx:subscribed(<<"clientId">>, <<"topic">>),
Topics = emqx:topics(),
t_sub_unsub(_) ->
ok = emqx_broker:subscribe(<<"topic">>, <<"clientId">>),
ok = emqx_broker:subscribe(<<"topic/1">>, <<"clientId">>, #{qos => 1}),
ok = emqx_broker:subscribe(<<"topic/2">>, <<"clientId">>, #{qos => 2}),
true = emqx_broker:subscribed(<<"clientId">>, <<"topic">>),
Topics = emqx_broker:topics(),
lists:foreach(fun(Topic) ->
?assert(lists:member(Topic, Topics))
end, Topics),
ok = emqx:unsubscribe(<<"topic">>),
ok = emqx:unsubscribe(<<"topic/1">>),
ok = emqx:unsubscribe(<<"topic/2">>).
ok = emqx_broker:unsubscribe(<<"topic">>),
ok = emqx_broker:unsubscribe(<<"topic/1">>),
ok = emqx_broker:unsubscribe(<<"topic/2">>).
publish(_) ->
t_publish(_) ->
Msg = emqx_message:make(ct, <<"test/pubsub">>, <<"hello">>),
ok = emqx:subscribe(<<"test/+">>),
ok = emqx_broker:subscribe(<<"test/+">>),
timer:sleep(10),
emqx:publish(Msg),
?assert(receive {dispatch, <<"test/+">>, #message{payload = <<"hello">>}} -> true after 100 -> false end).
emqx_broker:publish(Msg),
?assert(receive {deliver, <<"test/+">>, #message{payload = <<"hello">>}} -> true after 100 -> false end).
dispatch_with_no_sub(_) ->
t_dispatch_with_no_sub(_) ->
Msg = emqx_message:make(ct, <<"no_subscribers">>, <<"hello">>),
Delivery = #delivery{sender = self(), message = Msg, results = []},
?assertEqual(Delivery, emqx_broker:route([{<<"no_subscribers">>, node()}], Delivery)).
pubsub(_) ->
t_pubsub(_) ->
true = emqx:is_running(node()),
Self = self(),
Subscriber = <<"clientId">>,
ok = emqx:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 1 }),
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 1 }),
#{qos := 1} = ets:lookup_element(emqx_suboption, {Self, <<"a/b/c">>}, 2),
#{qos := 1} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
true = emqx_broker:set_subopts(<<"a/b/c">>, #{qos => 0}),
#{qos := 0} = emqx_broker:get_subopts(Subscriber, <<"a/b/c">>),
ok = emqx:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 2 }),
ok = emqx_broker:subscribe(<<"a/b/c">>, Subscriber, #{ qos => 2 }),
%% ct:log("Emq Sub: ~p.~n", [ets:lookup(emqx_suboption, {<<"a/b/c">>, Subscriber})]),
timer:sleep(10),
[Self] = emqx_broker:subscribers(<<"a/b/c">>),
emqx:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
emqx_broker:publish(
emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(
receive {dispatch, <<"a/b/c">>, _ } ->
receive {deliver, <<"a/b/c">>, _ } ->
true;
P ->
ct:log("Receive Message: ~p~n",[P])
@ -102,62 +108,43 @@ pubsub(_) ->
false
end),
spawn(fun() ->
emqx:subscribe(<<"a/b/c">>),
emqx:subscribe(<<"c/d/e">>),
emqx_broker:subscribe(<<"a/b/c">>),
emqx_broker:subscribe(<<"c/d/e">>),
timer:sleep(10),
emqx:unsubscribe(<<"a/b/c">>)
emqx_broker:unsubscribe(<<"a/b/c">>)
end),
timer:sleep(20),
emqx:unsubscribe(<<"a/b/c">>).
emqx_broker:unsubscribe(<<"a/b/c">>).
t_shared_subscribe(_) ->
emqx:subscribe("$share/group2/topic2"),
emqx:subscribe("$queue/topic3"),
emqx_broker:subscribe(<<"$share/group2/topic2">>),
emqx_broker:subscribe(<<"$queue/topic3">>),
timer:sleep(10),
ct:log("share subscriptions: ~p~n", [emqx:subscriptions(self())]),
?assertEqual(2, length(emqx:subscriptions(self()))),
emqx:unsubscribe("$share/group2/topic2"),
emqx:unsubscribe("$queue/topic3"),
?assertEqual(0, length(emqx:subscriptions(self()))).
ct:pal("Share subscriptions: ~p",
[emqx_broker:subscriptions(self())]),
?assertEqual(2, length(emqx_broker:subscriptions(self()))),
emqx_broker:unsubscribe(<<"$share/group2/topic2">>),
emqx_broker:unsubscribe(<<"$queue/topic3">>),
?assertEqual(0, length(emqx_broker:subscriptions(self()))).
'pubsub#'(_) ->
emqx:subscribe(<<"a/#">>),
't_pubsub#'(_) ->
emqx_broker:subscribe(<<"a/#">>),
timer:sleep(10),
emqx:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {dispatch, <<"a/#">>, _} -> true after 100 -> false end),
emqx:unsubscribe(<<"a/#">>).
emqx_broker:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {deliver, <<"a/#">>, _} -> true after 100 -> false end),
emqx_broker:unsubscribe(<<"a/#">>).
'pubsub+'(_) ->
emqx:subscribe(<<"a/+/+">>),
timer:sleep(10),
emqx:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {dispatch, <<"a/+/+">>, _} -> true after 100 -> false end),
emqx:unsubscribe(<<"a/+/+">>).
%%--------------------------------------------------------------------
%% Session Group
%%--------------------------------------------------------------------
start_session(_) ->
ClientId = <<"clientId">>,
{ok, ClientPid} = emqx_mock_client:start_link(ClientId),
{ok, SessPid} = emqx_mock_client:open_session(ClientPid, ClientId, internal),
Message1 = emqx_message:make(<<"clientId">>, 2, <<"topic">>, <<"hello">>),
emqx_session:publish(SessPid, 1, Message1),
emqx_session:pubrel(SessPid, 2, reasoncode),
emqx_session:subscribe(SessPid, [{<<"topic/session">>, #{qos => 2}}]),
Message2 = emqx_message:make(<<"clientId">>, 1, <<"topic/session">>, <<"test">>),
emqx_session:publish(SessPid, 3, Message2),
emqx_session:unsubscribe(SessPid, [{<<"topic/session">>, []}]),
%% emqx_mock_client:stop(ClientPid).
emqx_mock_client:close_session(ClientPid).
%%--------------------------------------------------------------------
%% Broker Group
%%--------------------------------------------------------------------
't_pubsub+'(_) ->
emqx_broker:subscribe(<<"a/+/+">>),
timer:sleep(10), %% TODO: why sleep?
emqx_broker:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)),
?assert(receive {deliver, <<"a/+/+">>, _} -> true after 100 -> false end),
emqx_broker:unsubscribe(<<"a/+/+">>).
%%--------------------------------------------------------------------
%% Metric Group
%%--------------------------------------------------------------------
inc_dec_metric(_) ->
emqx_metrics:inc('messages.retained', 10),
emqx_metrics:dec('messages.retained', 10).
@ -168,4 +155,5 @@ inc_dec_metric(_) ->
set_get_stat(_) ->
emqx_stats:setstat('retained.max', 99),
99 = emqx_stats:getstat('retained.max').
?assertEqual(99, emqx_stats:getstat('retained.max')).

View File

@ -1,74 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_channel_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() ->
[t_connect_api].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_connect_api(_Config) ->
{ok, T1} = emqx_client:start_link([{host, "localhost"},
{client_id, <<"client1">>},
{username, <<"testuser1">>},
{password, <<"pass1">>}]),
{ok, _} = emqx_client:connect(T1),
CPid = emqx_cm:lookup_conn_pid(<<"client1">>),
ConnStats = emqx_channel:stats(CPid),
ok = t_stats(ConnStats),
ConnAttrs = emqx_channel:attrs(CPid),
ok = t_attrs(ConnAttrs),
ConnInfo = emqx_channel:info(CPid),
ok = t_info(ConnInfo),
SessionPid = emqx_channel:session(CPid),
true = is_pid(SessionPid),
emqx_client:disconnect(T1).
t_info(ConnInfo) ->
?assertEqual(tcp, maps:get(socktype, ConnInfo)),
?assertEqual(running, maps:get(conn_state, ConnInfo)),
?assertEqual(<<"client1">>, maps:get(client_id, ConnInfo)),
?assertEqual(<<"testuser1">>, maps:get(username, ConnInfo)),
?assertEqual(<<"MQTT">>, maps:get(proto_name, ConnInfo)).
t_attrs(AttrsData) ->
?assertEqual(<<"client1">>, maps:get(client_id, AttrsData)),
?assertEqual(emqx_channel, maps:get(conn_mod, AttrsData)),
?assertEqual(<<"testuser1">>, maps:get(username, AttrsData)).
t_stats(StatsData) ->
?assertEqual(true, proplists:get_value(recv_oct, StatsData) >= 0),
?assertEqual(true, proplists:get_value(mailbox_len, StatsData) >= 0),
?assertEqual(true, proplists:get_value(heap_size, StatsData) >= 0),
?assertEqual(true, proplists:get_value(reductions, StatsData) >=0),
?assertEqual(true, proplists:get_value(recv_pkt, StatsData) =:=1),
?assertEqual(true, proplists:get_value(recv_msg, StatsData) >=0),
?assertEqual(true, proplists:get_value(send_pkt, StatsData) =:=1).

View File

@ -1,209 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_client_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-import(lists, [nth/2]).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(TOPICS, [<<"TopicA">>, <<"TopicA/B">>, <<"Topic/C">>, <<"TopicA/C">>,
<<"/TopicA">>]).
-define(WILD_TOPICS, [<<"TopicA/+">>, <<"+/C">>, <<"#">>, <<"/#">>, <<"/+">>,
<<"+/+">>, <<"TopicA/#">>]).
all() ->
[{group, mqttv4}].
groups() ->
[{mqttv4, [non_parallel_tests],
[basic_test,
will_message_test,
offline_message_queueing_test,
overlapping_subscriptions_test,
%% keepalive_test,
redelivery_on_reconnect_test,
%% subscribe_failure_test,
dollar_topics_test]}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
receive_messages(Count) ->
receive_messages(Count, []).
receive_messages(0, Msgs) ->
Msgs;
receive_messages(Count, Msgs) ->
receive
{publish, Msg} ->
receive_messages(Count-1, [Msg|Msgs]);
_Other ->
receive_messages(Count, Msgs)
after 100 ->
Msgs
end.
basic_test(_Config) ->
Topic = nth(1, ?TOPICS),
ct:print("Basic test starting"),
{ok, C} = emqx_client:start_link(),
{ok, _} = emqx_client:connect(C),
{ok, _, [1]} = emqx_client:subscribe(C, Topic, qos1),
{ok, _, [2]} = emqx_client:subscribe(C, Topic, qos2),
{ok, _} = emqx_client:publish(C, Topic, <<"qos 2">>, 2),
{ok, _} = emqx_client:publish(C, Topic, <<"qos 2">>, 2),
{ok, _} = emqx_client:publish(C, Topic, <<"qos 2">>, 2),
?assertEqual(3, length(receive_messages(3))),
ok = emqx_client:disconnect(C).
will_message_test(_Config) ->
{ok, C1} = emqx_client:start_link([{clean_start, true},
{will_topic, nth(3, ?TOPICS)},
{will_payload, <<"client disconnected">>},
{keepalive, 2}]),
{ok, _} = emqx_client:connect(C1),
{ok, C2} = emqx_client:start_link(),
{ok, _} = emqx_client:connect(C2),
{ok, _, [2]} = emqx_client:subscribe(C2, nth(3, ?TOPICS), 2),
timer:sleep(10),
ok = emqx_client:stop(C1),
timer:sleep(5),
?assertEqual(1, length(receive_messages(1))),
ok = emqx_client:disconnect(C2),
ct:print("Will message test succeeded").
offline_message_queueing_test(_) ->
{ok, C1} = emqx_client:start_link([{clean_start, false},
{client_id, <<"c1">>}]),
{ok, _} = emqx_client:connect(C1),
{ok, _, [2]} = emqx_client:subscribe(C1, nth(6, ?WILD_TOPICS), 2),
ok = emqx_client:disconnect(C1),
{ok, C2} = emqx_client:start_link([{clean_start, true},
{client_id, <<"c2">>}]),
{ok, _} = emqx_client:connect(C2),
ok = emqx_client:publish(C2, nth(2, ?TOPICS), <<"qos 0">>, 0),
{ok, _} = emqx_client:publish(C2, nth(3, ?TOPICS), <<"qos 1">>, 1),
{ok, _} = emqx_client:publish(C2, nth(4, ?TOPICS), <<"qos 2">>, 2),
timer:sleep(10),
emqx_client:disconnect(C2),
{ok, C3} = emqx_client:start_link([{clean_start, false},
{client_id, <<"c1">>}]),
{ok, _} = emqx_client:connect(C3),
timer:sleep(10),
emqx_client:disconnect(C3),
?assertEqual(3, length(receive_messages(3))).
overlapping_subscriptions_test(_) ->
{ok, C} = emqx_client:start_link([]),
{ok, _} = emqx_client:connect(C),
{ok, _, [2, 1]} = emqx_client:subscribe(C, [{nth(7, ?WILD_TOPICS), 2},
{nth(1, ?WILD_TOPICS), 1}]),
timer:sleep(10),
{ok, _} = emqx_client:publish(C, nth(4, ?TOPICS), <<"overlapping topic filters">>, 2),
timer:sleep(10),
Num = length(receive_messages(2)),
?assert(lists:member(Num, [1, 2])),
if
Num == 1 ->
ct:print("This server is publishing one message for all
matching overlapping subscriptions, not one for each.");
Num == 2 ->
ct:print("This server is publishing one message per each
matching overlapping subscription.");
true -> ok
end,
emqx_client:disconnect(C).
%% keepalive_test(_) ->
%% ct:print("Keepalive test starting"),
%% {ok, C1, _} = emqx_client:start_link([{clean_start, true},
%% {keepalive, 5},
%% {will_flag, true},
%% {will_topic, nth(5, ?TOPICS)},
%% %% {will_qos, 2},
%% {will_payload, <<"keepalive expiry">>}]),
%% ok = emqx_client:pause(C1),
%% {ok, C2, _} = emqx_client:start_link([{clean_start, true},
%% {keepalive, 0}]),
%% {ok, _, [2]} = emqx_client:subscribe(C2, nth(5, ?TOPICS), 2),
%% ok = emqx_client:disconnect(C2),
%% ?assertEqual(1, length(receive_messages(1))),
%% ct:print("Keepalive test succeeded").
redelivery_on_reconnect_test(_) ->
ct:print("Redelivery on reconnect test starting"),
{ok, C1} = emqx_client:start_link([{clean_start, false},
{client_id, <<"c">>}]),
{ok, _} = emqx_client:connect(C1),
{ok, _, [2]} = emqx_client:subscribe(C1, nth(7, ?WILD_TOPICS), 2),
timer:sleep(10),
ok = emqx_client:pause(C1),
{ok, _} = emqx_client:publish(C1, nth(2, ?TOPICS), <<>>,
[{qos, 1}, {retain, false}]),
{ok, _} = emqx_client:publish(C1, nth(4, ?TOPICS), <<>>,
[{qos, 2}, {retain, false}]),
timer:sleep(10),
ok = emqx_client:disconnect(C1),
?assertEqual(0, length(receive_messages(2))),
{ok, C2} = emqx_client:start_link([{clean_start, false},
{client_id, <<"c">>}]),
{ok, _} = emqx_client:connect(C2),
timer:sleep(10),
ok = emqx_client:disconnect(C2),
?assertEqual(2, length(receive_messages(2))).
%% subscribe_failure_test(_) ->
%% ct:print("Subscribe failure test starting"),
%% {ok, C, _} = emqx_client:start_link([]),
%% {ok, _, [2]} = emqx_client:subscribe(C, <<"$SYS/#">>, 2),
%% timer:sleep(10),
%% ct:print("Subscribe failure test succeeded").
dollar_topics_test(_) ->
ct:print("$ topics test starting"),
{ok, C} = emqx_client:start_link([{clean_start, true},
{keepalive, 0}]),
{ok, _} = emqx_client:connect(C),
{ok, _, [1]} = emqx_client:subscribe(C, nth(6, ?WILD_TOPICS), 1),
{ok, _} = emqx_client:publish(C, << <<"$">>/binary, (nth(2, ?TOPICS))/binary>>,
<<"test">>, [{qos, 1}, {retain, false}]),
timer:sleep(10),
?assertEqual(0, length(receive_messages(1))),
ok = emqx_client:disconnect(C),
ct:print("$ topics test succeeded").

View File

@ -1,71 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_cm_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [{group, cm}].
groups() ->
[{cm, [non_parallel_tests],
[t_get_set_conn_attrs,
t_get_set_conn_stats,
t_lookup_conn_pid]}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
init_per_testcase(_TestCase, Config) ->
register_connection(),
Config.
end_per_testcase(_TestCase, _Config) ->
unregister_connection(),
ok.
t_get_set_conn_attrs(_) ->
?assert(emqx_cm:set_conn_attrs(<<"conn1">>, [{port, 8080}, {ip, "192.168.0.1"}])),
?assert(emqx_cm:set_conn_attrs(<<"conn2">>, self(), [{port, 8080}, {ip, "192.168.0.2"}])),
?assertEqual([{port, 8080}, {ip, "192.168.0.1"}], emqx_cm:get_conn_attrs(<<"conn1">>)),
?assertEqual([{port, 8080}, {ip, "192.168.0.2"}], emqx_cm:get_conn_attrs(<<"conn2">>, self())).
t_get_set_conn_stats(_) ->
?assert(emqx_cm:set_conn_stats(<<"conn1">>, [{count, 1}, {max, 2}])),
?assert(emqx_cm:set_conn_stats(<<"conn2">>, self(), [{count, 1}, {max, 2}])),
?assertEqual([{count, 1}, {max, 2}], emqx_cm:get_conn_stats(<<"conn1">>)),
?assertEqual([{count, 1}, {max, 2}], emqx_cm:get_conn_stats(<<"conn2">>, self())).
t_lookup_conn_pid(_) ->
?assertEqual(ok, emqx_cm:register_connection(<<"conn1">>, self())),
?assertEqual(self(), emqx_cm:lookup_conn_pid(<<"conn1">>)).
register_connection() ->
?assertEqual(ok, emqx_cm:register_connection(<<"conn1">>)),
?assertEqual(ok, emqx_cm:register_connection(<<"conn2">>, self())).
unregister_connection() ->
?assertEqual(ok, emqx_cm:unregister_connection(<<"conn1">>)),
?assertEqual(ok, emqx_cm:unregister_connection(<<"conn2">>, self())).

View File

@ -1,62 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_flapping_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
all() ->
[t_flapping].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
prepare_for_test(),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_flapping(_Config) ->
process_flag(trap_exit, true),
flapping_connect(5),
{ok, C} = emqx_client:start_link([{client_id, <<"Client">>}]),
{error, _} = emqx_client:connect(C),
receive
{'EXIT', Client, _Reason} ->
ct:log("receive exit signal, Client: ~p", [Client])
after 1000 ->
ct:log("timeout")
end.
flapping_connect(Times) ->
[flapping_connect() || _ <- lists:seq(1, Times)].
flapping_connect() ->
{ok, C} = emqx_client:start_link([{client_id, <<"Client">>}]),
{ok, _} = emqx_client:connect(C),
ok = emqx_client:disconnect(C).
prepare_for_test() ->
emqx_zone:set_env(external, enable_flapping_detect, true),
emqx_zone:set_env(external, flapping_threshold, {10, 60}),
emqx_zone:set_env(external, flapping_expiry_interval, 3600).

View File

@ -37,60 +37,60 @@ all() ->
groups() ->
[{connect, [parallel],
[serialize_parse_connect,
serialize_parse_v3_connect,
serialize_parse_v4_connect,
serialize_parse_v5_connect,
serialize_parse_connect_without_clientid,
serialize_parse_connect_with_will,
serialize_parse_bridge_connect
[t_serialize_parse_connect,
t_serialize_parse_v3_connect,
t_serialize_parse_v4_connect,
t_serialize_parse_v5_connect,
t_serialize_parse_connect_without_clientid,
t_serialize_parse_connect_with_will,
t_serialize_parse_bridge_connect
]},
{connack, [parallel],
[serialize_parse_connack,
serialize_parse_connack_v5
[t_serialize_parse_connack,
t_serialize_parse_connack_v5
]},
{publish, [parallel],
[serialize_parse_qos0_publish,
serialize_parse_qos1_publish,
serialize_parse_qos2_publish,
serialize_parse_publish_v5
[t_serialize_parse_qos0_publish,
t_serialize_parse_qos1_publish,
t_serialize_parse_qos2_publish,
t_serialize_parse_publish_v5
]},
{puback, [parallel],
[serialize_parse_puback,
serialize_parse_puback_v5,
serialize_parse_pubrec,
serialize_parse_pubrec_v5,
serialize_parse_pubrel,
serialize_parse_pubrel_v5,
serialize_parse_pubcomp,
serialize_parse_pubcomp_v5
[t_serialize_parse_puback,
t_serialize_parse_puback_v5,
t_serialize_parse_pubrec,
t_serialize_parse_pubrec_v5,
t_serialize_parse_pubrel,
t_serialize_parse_pubrel_v5,
t_serialize_parse_pubcomp,
t_serialize_parse_pubcomp_v5
]},
{subscribe, [parallel],
[serialize_parse_subscribe,
serialize_parse_subscribe_v5
[t_serialize_parse_subscribe,
t_serialize_parse_subscribe_v5
]},
{suback, [parallel],
[serialize_parse_suback,
serialize_parse_suback_v5
[t_serialize_parse_suback,
t_serialize_parse_suback_v5
]},
{unsubscribe, [parallel],
[serialize_parse_unsubscribe,
serialize_parse_unsubscribe_v5
[t_serialize_parse_unsubscribe,
t_serialize_parse_unsubscribe_v5
]},
{unsuback, [parallel],
[serialize_parse_unsuback,
serialize_parse_unsuback_v5
[t_serialize_parse_unsuback,
t_serialize_parse_unsuback_v5
]},
{ping, [parallel],
[serialize_parse_pingreq,
serialize_parse_pingresp
[t_serialize_parse_pingreq,
t_serialize_parse_pingresp
]},
{disconnect, [parallel],
[serialize_parse_disconnect,
serialize_parse_disconnect_v5
[t_serialize_parse_disconnect,
t_serialize_parse_disconnect_v5
]},
{auth, [parallel],
[serialize_parse_auth_v5]
[t_serialize_parse_auth_v5]
}].
init_per_suite(Config) ->
@ -105,7 +105,7 @@ init_per_group(_Group, Config) ->
end_per_group(_Group, _Config) ->
ok.
serialize_parse_connect(_) ->
t_serialize_parse_connect(_) ->
Packet1 = ?CONNECT_PACKET(#mqtt_packet_connect{}),
?assertEqual(Packet1, parse_serialize(Packet1)),
Packet2 = ?CONNECT_PACKET(#mqtt_packet_connect{
@ -119,7 +119,7 @@ serialize_parse_connect(_) ->
}),
?assertEqual(Packet2, parse_serialize(Packet2)).
serialize_parse_v3_connect(_) ->
t_serialize_parse_v3_connect(_) ->
Bin = <<16,37,0,6,77,81,73,115,100,112,3,2,0,60,0,23,109,111,115,
113,112,117, 98,47,49,48,52,53,49,45,105,77,97,99,46,108,
111,99,97>>,
@ -132,7 +132,7 @@ serialize_parse_v3_connect(_) ->
}),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_v4_connect(_) ->
t_serialize_parse_v4_connect(_) ->
Bin = <<16,35,0,4,77,81,84,84,4,2,0,60,0,23,109,111,115,113,112,117,
98,47,49,48,52,53,49,45,105,77,97,99,46,108,111,99,97>>,
Packet = ?CONNECT_PACKET(#mqtt_packet_connect{proto_ver = 4,
@ -143,7 +143,7 @@ serialize_parse_v4_connect(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_v5_connect(_) ->
t_serialize_parse_v5_connect(_) ->
Props = #{'Session-Expiry-Interval' => 60,
'Receive-Maximum' => 100,
'Maximum-QoS' => ?QOS_2,
@ -183,7 +183,7 @@ serialize_parse_v5_connect(_) ->
}),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_connect_without_clientid(_) ->
t_serialize_parse_connect_without_clientid(_) ->
Bin = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>,
Packet = ?CONNECT_PACKET(
#mqtt_packet_connect{proto_ver = 4,
@ -195,7 +195,7 @@ serialize_parse_connect_without_clientid(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_connect_with_will(_) ->
t_serialize_parse_connect_with_will(_) ->
Bin = <<16,67,0,6,77,81,73,115,100,112,3,206,0,60,0,23,109,111,115,113,112,
117,98,47,49,48,52,53,50,45,105,77,97,99,46,108,111,99,97,0,5,47,119,
105,108,108,0,7,119,105,108,108,109,115,103,0,4,116,101,115,116,0,6,
@ -217,7 +217,7 @@ serialize_parse_connect_with_will(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_bridge_connect(_) ->
t_serialize_parse_bridge_connect(_) ->
Bin = <<16,86,0,6,77,81,73,115,100,112,131,44,0,60,0,19,67,95,48,48,58,48,67,
58,50,57,58,50,66,58,55,55,58,53,50,0,48,36,83,89,83,47,98,114,111,107,
101,114,47,99,111,110,110,101,99,116,105,111,110,47,67,95,48,48,58,48,
@ -239,12 +239,12 @@ serialize_parse_bridge_connect(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_connack(_) ->
t_serialize_parse_connack(_) ->
Packet = ?CONNACK_PACKET(?RC_SUCCESS),
?assertEqual(<<32,2,0,0>>, serialize_to_binary(Packet)),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_connack_v5(_) ->
t_serialize_parse_connack_v5(_) ->
Props = #{'Session-Expiry-Interval' => 60,
'Receive-Maximum' => 100,
'Maximum-QoS' => ?QOS_2,
@ -265,7 +265,7 @@ serialize_parse_connack_v5(_) ->
Packet = ?CONNACK_PACKET(?RC_SUCCESS, 0, Props),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_qos0_publish(_) ->
t_serialize_parse_qos0_publish(_) ->
Bin = <<48,14,0,7,120,120,120,47,121,121,121,104,101,108,108,111>>,
Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
dup = false,
@ -277,7 +277,7 @@ serialize_parse_qos0_publish(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_qos1_publish(_) ->
t_serialize_parse_qos1_publish(_) ->
Bin = <<50,13,0,5,97,47,98,47,99,0,1,104,97,104,97>>,
Packet = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
dup = false,
@ -289,11 +289,11 @@ serialize_parse_qos1_publish(_) ->
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_qos2_publish(_) ->
t_serialize_parse_qos2_publish(_) ->
Packet = ?PUBLISH_PACKET(?QOS_2, <<"Topic">>, 1, payload()),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_publish_v5(_) ->
t_serialize_parse_publish_v5(_) ->
Props = #{'Payload-Format-Indicator' => 1,
'Message-Expiry-Interval' => 60,
'Topic-Alias' => 16#AB,
@ -304,45 +304,45 @@ serialize_parse_publish_v5(_) ->
Packet = ?PUBLISH_PACKET(?QOS_1, <<"$share/group/topic">>, 1, Props, <<"payload">>),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_puback(_) ->
t_serialize_parse_puback(_) ->
Packet = ?PUBACK_PACKET(1),
?assertEqual(<<64,2,0,1>>, serialize_to_binary(Packet)),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_puback_v5(_) ->
t_serialize_parse_puback_v5(_) ->
Packet = ?PUBACK_PACKET(16, ?RC_SUCCESS, #{'Reason-String' => <<"success">>}),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_pubrec(_) ->
t_serialize_parse_pubrec(_) ->
Packet = ?PUBREC_PACKET(1),
?assertEqual(<<5:4,0:4,2,0,1>>, serialize_to_binary(Packet)),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_pubrec_v5(_) ->
t_serialize_parse_pubrec_v5(_) ->
Packet = ?PUBREC_PACKET(16, ?RC_SUCCESS, #{'Reason-String' => <<"success">>}),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_pubrel(_) ->
t_serialize_parse_pubrel(_) ->
Packet = ?PUBREL_PACKET(1),
Bin = serialize_to_binary(Packet),
?assertEqual(<<6:4,2:4,2,0,1>>, Bin),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_pubrel_v5(_) ->
t_serialize_parse_pubrel_v5(_) ->
Packet = ?PUBREL_PACKET(16, ?RC_SUCCESS, #{'Reason-String' => <<"success">>}),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_pubcomp(_) ->
t_serialize_parse_pubcomp(_) ->
Packet = ?PUBCOMP_PACKET(1),
Bin = serialize_to_binary(Packet),
?assertEqual(<<7:4,0:4,2,0,1>>, Bin),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_pubcomp_v5(_) ->
t_serialize_parse_pubcomp_v5(_) ->
Packet = ?PUBCOMP_PACKET(16, ?RC_SUCCESS, #{'Reason-String' => <<"success">>}),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_subscribe(_) ->
t_serialize_parse_subscribe(_) ->
%% SUBSCRIBE(Q1, R0, D0, PacketId=2, TopicTable=[{<<"TopicA">>,2}])
Bin = <<130,11,0,2,0,6,84,111,112,105,99,65,2>>,
TopicOpts = #{nl => 0 , rap => 0, rc => 0, rh => 0, qos => 2},
@ -352,61 +352,61 @@ serialize_parse_subscribe(_) ->
%%ct:log("Bin: ~p, Packet: ~p ~n", [Packet, parse(Bin)]),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_subscribe_v5(_) ->
t_serialize_parse_subscribe_v5(_) ->
TopicFilters = [{<<"TopicQos0">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0}},
{<<"TopicQos1">>, #{rh => 1, qos => ?QOS_2, rap => 0, nl => 0, rc => 0}}],
Packet = ?SUBSCRIBE_PACKET(3, #{'Subscription-Identifier' => 16#FFFFFFF}, TopicFilters),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_suback(_) ->
t_serialize_parse_suback(_) ->
Packet = ?SUBACK_PACKET(10, [?QOS_0, ?QOS_1, 128]),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_suback_v5(_) ->
t_serialize_parse_suback_v5(_) ->
Packet = ?SUBACK_PACKET(1, #{'Reason-String' => <<"success">>,
'User-Property' => [{<<"key">>, <<"value">>}]},
[?QOS_0, ?QOS_1, 128]),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_unsubscribe(_) ->
t_serialize_parse_unsubscribe(_) ->
%% UNSUBSCRIBE(Q1, R0, D0, PacketId=2, TopicTable=[<<"TopicA">>])
Packet = ?UNSUBSCRIBE_PACKET(2, [<<"TopicA">>]),
Bin = <<162,10,0,2,0,6,84,111,112,105,99,65>>,
?assertEqual(Bin, serialize_to_binary(Packet)),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(Bin)).
serialize_parse_unsubscribe_v5(_) ->
t_serialize_parse_unsubscribe_v5(_) ->
Props = #{'User-Property' => [{<<"key">>, <<"val">>}]},
Packet = ?UNSUBSCRIBE_PACKET(10, Props, [<<"Topic1">>, <<"Topic2">>]),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_unsuback(_) ->
t_serialize_parse_unsuback(_) ->
Packet = ?UNSUBACK_PACKET(10),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_unsuback_v5(_) ->
t_serialize_parse_unsuback_v5(_) ->
Packet = ?UNSUBACK_PACKET(10, #{'Reason-String' => <<"Not authorized">>,
'User-Property' => [{<<"key">>, <<"val">>}]},
[16#87, 16#87, 16#87]),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_pingreq(_) ->
t_serialize_parse_pingreq(_) ->
PingReq = ?PACKET(?PINGREQ),
?assertEqual(PingReq, parse_serialize(PingReq)).
serialize_parse_pingresp(_) ->
t_serialize_parse_pingresp(_) ->
PingResp = ?PACKET(?PINGRESP),
?assertEqual(PingResp, parse_serialize(PingResp)).
parse_disconnect(_) ->
t_parse_disconnect(_) ->
Packet = ?DISCONNECT_PACKET(?RC_SUCCESS),
?assertMatch({ok, Packet, <<>>, _}, emqx_frame:parse(<<224, 0>>)).
serialize_parse_disconnect(_) ->
t_serialize_parse_disconnect(_) ->
Packet = ?DISCONNECT_PACKET(?RC_SUCCESS),
?assertEqual(Packet, parse_serialize(Packet)).
serialize_parse_disconnect_v5(_) ->
t_serialize_parse_disconnect_v5(_) ->
Packet = ?DISCONNECT_PACKET(?RC_SUCCESS,
#{'Session-Expiry-Interval' => 60,
'Reason-String' => <<"server_moved">>,
@ -414,7 +414,7 @@ serialize_parse_disconnect_v5(_) ->
}),
?assertEqual(Packet, parse_serialize(Packet, #{version => ?MQTT_PROTO_V5})).
serialize_parse_auth_v5(_) ->
t_serialize_parse_auth_v5(_) ->
Packet = ?AUTH_PACKET(?RC_SUCCESS,
#{'Authentication-Method' => <<"oauth2">>,
'Authentication-Data' => <<"3zekkd">>,
@ -427,7 +427,8 @@ parse_serialize(Packet) ->
parse_serialize(Packet, #{}).
parse_serialize(Packet, Opts) when is_map(Opts) ->
Bin = iolist_to_binary(emqx_frame:serialize(Packet, Opts)),
Ver = maps:get(version, Opts, ?MQTT_PROTO_V4),
Bin = iolist_to_binary(emqx_frame:serialize(Packet, Ver)),
ParseState = emqx_frame:initial_parse_state(Opts),
{ok, NPacket, <<>>, _} = emqx_frame:parse(Bin, ParseState),
NPacket.

View File

@ -21,8 +21,7 @@
-include_lib("eunit/include/eunit.hrl").
all() ->
[t_init, t_run, t_info, t_reset].
all() -> emqx_ct:all(?MODULE).
t_init(_) ->
?assertEqual(undefined, emqx_gc:init(false)),

View File

@ -16,21 +16,21 @@
-module(emqx_guid_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_guid_gen, t_guid_hexstr, t_guid_base62].
-include_lib("eunit/include/eunit.hrl").
all() -> emqx_ct:all(?MODULE).
t_guid_gen(_) ->
Guid1 = emqx_guid:gen(),
Guid2 = emqx_guid:gen(),
<<_:128>> = Guid1,
true = (Guid2 >= Guid1),
?assert((Guid2 >= Guid1)),
{Ts1, _, 0} = emqx_guid:new(),
Ts2 = emqx_guid:timestamp(emqx_guid:gen()),
true = Ts2 > Ts1.
?assert(Ts2 > Ts1).
t_guid_hexstr(_) ->
Guid = emqx_guid:gen(),

View File

@ -20,12 +20,10 @@
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() ->
[add_delete_hook, run_hook].
all() -> emqx_ct:all(?MODULE).
add_delete_hook(_) ->
t_add_del_hook(_) ->
{ok, _} = emqx_hooks:start_link(),
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []),
ok = emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, []),
@ -56,7 +54,7 @@ add_delete_hook(_) ->
?assertEqual([], emqx_hooks:lookup(emqx_hook)),
ok = emqx_hooks:stop().
run_hook(_) ->
t_run_hooks(_) ->
{ok, _} = emqx_hooks:start_link(),
ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun3/4, [init]),
ok = emqx:hook(foldl_hook, {?MODULE, hook_fun3, [init]}),
@ -86,6 +84,10 @@ run_hook(_) ->
ok = emqx_hooks:stop().
%%--------------------------------------------------------------------
%% Hook fun
%%--------------------------------------------------------------------
hook_fun1(arg) -> ok;
hook_fun1(_) -> error.
@ -104,7 +106,7 @@ 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_fun10(arg, Acc) -> {stop, [r10 | Acc]}.
hook_filter1(arg) -> true;
hook_filter1(_) -> false.
@ -112,6 +114,7 @@ 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(arg, _Acc, init_arg) -> true;
hook_filter2_1(arg1, _Acc, init_arg) -> true;
hook_filter2_1(_, _Acc, _IntArg) -> false.
hook_filter2_1(_, _Acc, _IntArg) -> false.

View File

@ -19,26 +19,73 @@
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_inflight_all].
-include_lib("eunit/include/eunit.hrl").
-include_lib("emqx_ct_helpers/include/emqx_ct.hrl").
t_inflight_all(_) ->
Empty = emqx_inflight:new(2),
true = emqx_inflight:is_empty(Empty),
2 = emqx_inflight:max_size(Empty),
false = emqx_inflight:contain(a, Empty),
none = emqx_inflight:lookup(a, Empty),
try emqx_inflight:update(a, 1, Empty) catch
error:Reason -> io:format("Reason: ~w~n", [Reason])
end,
0 = emqx_inflight:size(Empty),
Inflight1 = emqx_inflight:insert(a, 1, Empty),
Inflight2 = emqx_inflight:insert(b, 2, Inflight1),
2 = emqx_inflight:size(Inflight2),
true = emqx_inflight:is_full(Inflight2),
{value, 1} = emqx_inflight:lookup(a, Inflight1),
{value, 2} = emqx_inflight:lookup(a, emqx_inflight:update(a, 2, Inflight1)),
false = emqx_inflight:contain(a, emqx_inflight:delete(a, Inflight1)),
[1, 2] = emqx_inflight:values(Inflight2),
[{a, 1}, {b ,2}] = emqx_inflight:to_list(Inflight2),
[a, b] = emqx_inflight:window(Inflight2).
all() -> emqx_ct:all(?MODULE).
t_contain(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()),
?assert(emqx_inflight:contain(k, Inflight)),
?assertNot(emqx_inflight:contain(badkey, Inflight)).
t_lookup(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()),
?assertEqual({value, v}, emqx_inflight:lookup(k, Inflight)),
?assertEqual(none, emqx_inflight:lookup(badkey, Inflight)).
t_insert(_) ->
Inflight = emqx_inflight:insert(
b, 2, emqx_inflight:insert(
a, 1, emqx_inflight:new())),
?assertEqual(2, emqx_inflight:size(Inflight)),
?assertEqual({value, 1}, emqx_inflight:lookup(a, Inflight)),
?assertEqual({value, 2}, emqx_inflight:lookup(b, Inflight)),
?catch_error({key_exists, a}, emqx_inflight:insert(a, 1, Inflight)).
t_update(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()),
?assertEqual(Inflight, emqx_inflight:update(k, v, Inflight)),
?catch_error(function_clause, emqx_inflight:update(badkey, v, Inflight)).
t_resize(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new(2)),
?assertEqual(1, emqx_inflight:size(Inflight)),
?assertEqual(2, emqx_inflight:max_size(Inflight)),
Inflight1 = emqx_inflight:resize(4, Inflight),
?assertEqual(4, emqx_inflight:max_size(Inflight1)),
?assertEqual(1, emqx_inflight:size(Inflight)).
t_delete(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new(2)),
Inflight1 = emqx_inflight:delete(k, Inflight),
?assert(emqx_inflight:is_empty(Inflight1)),
?assertNot(emqx_inflight:contain(k, Inflight1)).
t_values(_) ->
Inflight = emqx_inflight:insert(
b, 2, emqx_inflight:insert(
a, 1, emqx_inflight:new())),
?assertEqual([1,2], emqx_inflight:values(Inflight)),
?assertEqual([{a,1},{b,2}], emqx_inflight:to_list(Inflight)).
t_is_full(_) ->
Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()),
?assertNot(emqx_inflight:is_full(Inflight)),
Inflight1 = emqx_inflight:insert(
b, 2, emqx_inflight:insert(
a, 1, emqx_inflight:new(2))),
?assert(emqx_inflight:is_full(Inflight1)).
t_is_empty(_) ->
Inflight = emqx_inflight:insert(a, 1, emqx_inflight:new(2)),
?assertNot(emqx_inflight:is_empty(Inflight)),
Inflight1 = emqx_inflight:delete(a, Inflight),
?assert(emqx_inflight:is_empty(Inflight1)).
t_window(_) ->
Inflight = emqx_inflight:insert(
b, 2, emqx_inflight:insert(
a, 1, emqx_inflight:new(2))),
[a, b] = emqx_inflight:window(Inflight).

View File

@ -19,21 +19,26 @@
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_decode_encode, t_safe_decode_encode].
-include_lib("eunit/include/eunit.hrl").
-define(DEC_OPTS, [{labels, atom}, return_maps]).
all() -> emqx_ct:all(?MODULE).
t_decode_encode(_) ->
JsonText = <<"{\"library\": \"jsx\", \"awesome\": true}">>,
JsonTerm = emqx_json:decode(JsonText),
JsonMaps = #{library => <<"jsx">>, awesome => true},
JsonMaps = emqx_json:decode(JsonText, [{labels, atom}, return_maps]),
JsonText = emqx_json:encode(JsonTerm, [{space, 1}]).
?assertEqual(JsonText, emqx_json:encode(JsonTerm, [{space, 1}])),
?assertEqual(JsonMaps, emqx_json:decode(JsonText, ?DEC_OPTS)).
t_safe_decode_encode(_) ->
JsonText = <<"{\"library\": \"jsx\", \"awesome\": true}">>,
{ok, JsonTerm} = emqx_json:safe_decode(JsonText),
JsonMaps = #{library => <<"jsx">>, awesome => true},
{ok, JsonMaps} = emqx_json:safe_decode(JsonText, [{labels, atom}, return_maps]),
{ok, JsonText} = emqx_json:safe_encode(JsonTerm, [{space, 1}]),
?assertEqual({ok, JsonText}, emqx_json:safe_encode(JsonTerm, [{space, 1}])),
?assertEqual({ok, JsonMaps}, emqx_json:safe_decode(JsonText, ?DEC_OPTS)),
BadJsonText = <<"{\"library\", \"awesome\": true}">>,
{error, _} = emqx_json:safe_decode(BadJsonText),
{error, _} = emqx_json:safe_encode({a, {b ,1}}).
?assertEqual({error, badarg}, emqx_json:safe_decode(BadJsonText)),
{error, badarg} = emqx_json:safe_encode({a, {b ,1}}).

View File

@ -19,13 +19,7 @@
-compile(export_all).
-compile(nowarn_export_all).
all() -> [{group, keepalive}].
groups() -> [{keepalive, [], [t_keepalive]}].
%%--------------------------------------------------------------------
%% Keepalive
%%--------------------------------------------------------------------
all() -> emqx_ct:all(?MODULE).
t_keepalive(_) ->
{ok, KA} = emqx_keepalive:start(fun() -> {ok, 1} end, 1, {keepalive, timeout}),
@ -38,7 +32,6 @@ keepalive_recv(KA, Acc) ->
{ok, KA1} -> keepalive_recv(KA1, [resumed | Acc]);
{error, timeout} -> [timeout | Acc]
end
after 4000 ->
Acc
after 4000 -> Acc
end.

View File

@ -1,175 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_lib_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-define(SOCKOPTS, [
binary,
{packet, raw},
{reuseaddr, true},
{backlog, 512},
{nodelay, true}
]).
-define(PQ, emqx_pqueue).
-define(BASE62, emqx_base62).
all() -> [{group, guid}, {group, opts},
{group, ?PQ}, {group, time},
{group, node}, {group, base62}].
groups() ->
[{guid, [], [guid_gen, guid_hexstr, guid_base62]},
{opts, [], [opts_merge]},
{?PQ, [], [priority_queue_plen,
priority_queue_out2]},
{time, [], [time_now_to_]},
{node, [], [node_is_aliving, node_parse_name]},
{base62, [], [base62_encode]}].
%%--------------------------------------------------------------------
%% emqx_guid
%%--------------------------------------------------------------------
guid_gen(_) ->
Guid1 = emqx_guid:gen(),
Guid2 = emqx_guid:gen(),
<<_:128>> = Guid1,
true = (Guid2 >= Guid1),
{Ts1, _, 0} = emqx_guid:new(),
Ts2 = emqx_guid:timestamp(emqx_guid:gen()),
true = Ts2 > Ts1.
guid_hexstr(_) ->
Guid = emqx_guid:gen(),
?assertEqual(Guid, emqx_guid:from_hexstr(emqx_guid:to_hexstr(Guid))).
guid_base62(_) ->
Guid = emqx_guid:gen(),
?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))).
%%--------------------------------------------------------------------
%% emqx_opts
%%--------------------------------------------------------------------
opts_merge(_) ->
Opts = emqx_misc:merge_opts(?SOCKOPTS, [raw,
binary,
{backlog, 1024},
{nodelay, false},
{max_clients, 1024},
{acceptors, 16}]),
1024 = proplists:get_value(backlog, Opts),
1024 = proplists:get_value(max_clients, Opts),
[binary, raw,
{acceptors, 16},
{backlog, 1024},
{max_clients, 1024},
{nodelay, false},
{packet, raw},
{reuseaddr, true}] = lists:sort(Opts).
%%--------------------------------------------------------------------
%% priority_queue
%%--------------------------------------------------------------------
priority_queue_plen(_) ->
Q = ?PQ:new(),
0 = ?PQ:plen(0, Q),
Q0 = ?PQ:in(z, Q),
1 = ?PQ:plen(0, Q0),
Q1 = ?PQ:in(x, 1, Q0),
1 = ?PQ:plen(1, Q1),
Q2 = ?PQ:in(y, 2, Q1),
1 = ?PQ:plen(2, Q2),
Q3 = ?PQ:in(z, 2, Q2),
2 = ?PQ:plen(2, Q3),
{_, Q4} = ?PQ:out(1, Q3),
0 = ?PQ:plen(1, Q4),
{_, Q5} = ?PQ:out(Q4),
1 = ?PQ:plen(2, Q5),
{_, Q6} = ?PQ:out(Q5),
0 = ?PQ:plen(2, Q6),
1 = ?PQ:len(Q6),
{_, Q7} = ?PQ:out(Q6),
0 = ?PQ:len(Q7).
priority_queue_out2(_) ->
Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}],
Q = ?PQ:new(),
Q0 = lists:foldl(
fun({El, P}, Acc) ->
?PQ:in(El, P, Acc);
(El, Acc) ->
?PQ:in(El, Acc)
end, Q, Els),
{Val, Q1} = ?PQ:out(Q0),
{value, d} = Val,
{Val1, Q2} = ?PQ:out(2, Q1),
{value, e} = Val1,
{Val2, Q3} = ?PQ:out(1, Q2),
{value, b} = Val2,
{Val3, Q4} = ?PQ:out(Q3),
{value, f} = Val3,
{Val4, Q5} = ?PQ:out(Q4),
{value, c} = Val4,
{Val5, Q6} = ?PQ:out(Q5),
{value, a} = Val5,
{empty, _Q7} = ?PQ:out(Q6).
%%--------------------------------------------------------------------
%% emqx_time
%%--------------------------------------------------------------------
time_now_to_(_) ->
emqx_time:seed(),
emqx_time:now_secs(),
emqx_time:now_ms().
%%--------------------------------------------------------------------
%% emqx_node
%%--------------------------------------------------------------------
node_is_aliving(_) ->
io:format("Node: ~p~n", [node()]),
true = ekka_node:is_aliving(node()),
false = ekka_node:is_aliving('x@127.0.0.1').
node_parse_name(_) ->
'a@127.0.0.1' = ekka_node:parse_name("a@127.0.0.1"),
'b@127.0.0.1' = ekka_node:parse_name("b").
%%--------------------------------------------------------------------
%% base62 encode decode
%%--------------------------------------------------------------------
base62_encode(_) ->
<<"10">> = ?BASE62:decode(?BASE62:encode(<<"10">>)),
<<"100">> = ?BASE62:decode(?BASE62:encode(<<"100">>)),
<<"9999">> = ?BASE62:decode(?BASE62:encode(<<"9999">>)),
<<"65535">> = ?BASE62:decode(?BASE62:encode(<<"65535">>)),
<<X:128/unsigned-big-integer>> = emqx_guid:gen(),
<<Y:128/unsigned-big-integer>> = emqx_guid:gen(),
X = ?BASE62:decode(?BASE62:encode(X), integer),
Y = ?BASE62:decode(?BASE62:encode(Y), integer),
<<"helloworld">> = ?BASE62:decode(?BASE62:encode("helloworld")),
"helloworld" = ?BASE62:decode(?BASE62:encode("helloworld", string), string).

View File

@ -19,16 +19,10 @@
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
all() ->
[start_stop_listeners,
restart_listeners].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
NewConfig = generate_config(),
@ -41,11 +35,11 @@ end_per_suite(_Config) ->
application:stop(esockd),
application:stop(cowboy).
start_stop_listeners(_) ->
t_start_stop_listeners(_) ->
ok = emqx_listeners:start(),
ok = emqx_listeners:stop().
restart_listeners(_) ->
t_restart_listeners(_) ->
ok = emqx_listeners:start(),
ok = emqx_listeners:stop(),
ok = emqx_listeners:restart(),
@ -95,3 +89,4 @@ get_base_dir(Module) ->
get_base_dir() ->
get_base_dir(?MODULE).

View File

@ -16,7 +16,9 @@
-module(emqx_message_SUITE).
-include("emqx.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
@ -32,17 +34,22 @@
, suite/0
]).
all() -> emqx_ct:all(?MODULE).
suite() ->
[{ct_hooks, [cth_surefire]}, {timetrap, {seconds, 30}}].
t_make(_) ->
Msg = emqx_message:make(<<"topic">>, <<"payload">>),
?assertEqual(0, emqx_message:qos(Msg)),
?assertEqual(?QOS_0, emqx_message:qos(Msg)),
?assertEqual(undefined, emqx_message:from(Msg)),
?assertEqual(<<"payload">>, emqx_message:payload(Msg)),
Msg1 = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>),
?assertEqual(0, emqx_message:qos(Msg1)),
?assertEqual(?QOS_0, emqx_message:qos(Msg1)),
?assertEqual(<<"topic">>, emqx_message:topic(Msg1)),
Msg2 = emqx_message:make(<<"clientid">>, ?QOS_2, <<"topic">>, <<"payload">>),
?assert(is_binary(emqx_message:id(Msg2))),
?assertEqual(2, emqx_message:qos(Msg2)),
?assertEqual(?QOS_2, emqx_message:qos(Msg2)),
?assertEqual(<<"clientid">>, emqx_message:from(Msg2)),
?assertEqual(<<"topic">>, emqx_message:topic(Msg2)),
?assertEqual(<<"payload">>, emqx_message:payload(Msg2)).
@ -86,21 +93,14 @@ t_expired(_) ->
t_to_map(_) ->
Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"topic">>, <<"payload">>),
List = [{id, Msg#message.id},
List = [{id, emqx_message:id(Msg)},
{qos, ?QOS_1},
{from, <<"clientid">>},
{flags, #{dup => false}},
{headers, #{}},
{topic, <<"topic">>},
{payload, <<"payload">>},
{timestamp, Msg#message.timestamp}],
{timestamp, emqx_message:timestamp(Msg)}],
?assertEqual(List, emqx_message:to_list(Msg)),
?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)).
all() ->
IsTestCase = fun("t_" ++ _) -> true; (_) -> false end,
[F || {F, _A} <- module_info(exports), IsTestCase(atom_to_list(F))].
suite() ->
[{ct_hooks, [cth_surefire]}, {timetrap, {seconds, 30}}].

View File

@ -22,55 +22,64 @@
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [t_inc_dec, t_inc_recv, t_inc_sent, t_trans].
all() -> emqx_ct:all(?MODULE).
t_inc_dec(_) ->
{ok, _} = emqx_metrics:start_link(),
?assertEqual(0, emqx_metrics:val('bytes.received')),
?assertEqual(0, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:inc('bytes.received'),
ok = emqx_metrics:inc('bytes.received', 2),
ok = emqx_metrics:inc('bytes.received', 2),
?assertEqual(5, emqx_metrics:val('bytes.received')),
ok = emqx_metrics:inc('messages.retained', 2),
ok = emqx_metrics:inc('messages.retained', 2),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:dec('messages.retained'),
ok = emqx_metrics:dec('messages.retained', 1),
?assertEqual(2, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:set('messages.retained', 3),
?assertEqual(3, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:stop().
with_metrics_server(
fun() ->
?assertEqual(0, emqx_metrics:val('bytes.received')),
?assertEqual(0, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:inc('bytes.received'),
ok = emqx_metrics:inc('bytes.received', 2),
ok = emqx_metrics:inc('bytes.received', 2),
?assertEqual(5, emqx_metrics:val('bytes.received')),
ok = emqx_metrics:inc('messages.retained', 2),
ok = emqx_metrics:inc('messages.retained', 2),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:dec('messages.retained'),
ok = emqx_metrics:dec('messages.retained', 1),
?assertEqual(2, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:set('messages.retained', 3),
?assertEqual(3, emqx_metrics:val('messages.retained'))
end).
t_inc_recv(_) ->
{ok, _} = emqx_metrics:start_link(),
ok = emqx_metrics:inc_recv(?PACKET(?CONNECT)),
?assertEqual(1, emqx_metrics:val('packets.received')),
?assertEqual(1, emqx_metrics:val('packets.connect.received')),
ok = emqx_metrics:stop().
with_metrics_server(
fun() ->
ok = emqx_metrics:inc_recv(?PACKET(?CONNECT)),
?assertEqual(1, emqx_metrics:val('packets.received')),
?assertEqual(1, emqx_metrics:val('packets.connect.received'))
end).
t_inc_sent(_) ->
{ok, _} = emqx_metrics:start_link(),
ok = emqx_metrics:inc_sent(?CONNACK_PACKET(0)),
?assertEqual(1, emqx_metrics:val('packets.sent')),
?assertEqual(1, emqx_metrics:val('packets.connack.sent')),
ok = emqx_metrics:stop().
with_metrics_server(
fun() ->
ok = emqx_metrics:inc_sent(?CONNACK_PACKET(0)),
?assertEqual(1, emqx_metrics:val('packets.sent')),
?assertEqual(1, emqx_metrics:val('packets.connack.sent'))
end).
t_trans(_) ->
with_metrics_server(
fun() ->
ok = emqx_metrics:trans(inc, 'bytes.received'),
ok = emqx_metrics:trans(inc, 'bytes.received', 2),
?assertEqual(0, emqx_metrics:val('bytes.received')),
ok = emqx_metrics:trans(inc, 'messages.retained', 2),
ok = emqx_metrics:trans(inc, 'messages.retained', 2),
?assertEqual(0, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:commit(),
?assertEqual(3, emqx_metrics:val('bytes.received')),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:trans(dec, 'messages.retained'),
ok = emqx_metrics:trans(dec, 'messages.retained', 1),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:commit(),
?assertEqual(2, emqx_metrics:val('messages.retained'))
end).
with_metrics_server(Fun) ->
{ok, _} = emqx_metrics:start_link(),
ok = emqx_metrics:trans(inc, 'bytes.received'),
ok = emqx_metrics:trans(inc, 'bytes.received', 2),
?assertEqual(0, emqx_metrics:val('bytes.received')),
ok = emqx_metrics:trans(inc, 'messages.retained', 2),
ok = emqx_metrics:trans(inc, 'messages.retained', 2),
?assertEqual(0, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:commit(),
?assertEqual(3, emqx_metrics:val('bytes.received')),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:trans(dec, 'messages.retained'),
ok = emqx_metrics:trans(dec, 'messages.retained', 1),
?assertEqual(4, emqx_metrics:val('messages.retained')),
ok = emqx_metrics:commit(),
?assertEqual(2, emqx_metrics:val('messages.retained')),
_ = Fun(),
ok = emqx_metrics:stop().

View File

@ -14,7 +14,11 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_misc_tests).
-module(emqx_misc_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-define(SOCKOPTS, [binary,
@ -23,8 +27,9 @@
{backlog, 512},
{nodelay, true}]).
all() -> emqx_ct:all(?MODULE).
t_merge_opts_test() ->
t_merge_opts() ->
Opts = emqx_misc:merge_opts(?SOCKOPTS, [raw,
binary,
{backlog, 1024},
@ -41,33 +46,47 @@ t_merge_opts_test() ->
{packet, raw},
{reuseaddr, true}] = lists:sort(Opts).
timer_cancel_flush_test() ->
t_timer_cancel_flush() ->
Timer = emqx_misc:start_timer(0, foo),
ok = emqx_misc:cancel_timer(Timer),
receive {timeout, Timer, foo} -> error(unexpected)
receive
{timeout, Timer, foo} ->
error(unexpected)
after 0 -> ok
end.
shutdown_disabled_test() ->
t_shutdown_disabled() ->
ok = drain(),
self() ! foo,
?assertEqual(continue, conn_proc_mng_policy(0)),
?assertEqual(continue, emqx_misc:conn_proc_mng_policy(0)),
receive foo -> ok end,
?assertEqual(hibernate, conn_proc_mng_policy(0)).
?assertEqual(hibernate, emqx_misc:conn_proc_mng_policy(0)).
message_queue_too_long_test() ->
t_message_queue_too_long() ->
ok = drain(),
self() ! foo,
self() ! bar,
?assertEqual({shutdown, message_queue_too_long},
conn_proc_mng_policy(1)),
emqx_misc:conn_proc_mng_policy(1)),
receive foo -> ok end,
?assertEqual(continue, conn_proc_mng_policy(1)),
?assertEqual(continue, emqx_misc:conn_proc_mng_policy(1)),
receive bar -> ok end.
conn_proc_mng_policy(L) ->
t_conn_proc_mng_policy(L) ->
emqx_misc:conn_proc_mng_policy(#{message_queue_len => L}).
t_proc_name(_) ->
'TODO'.
t_proc_stats(_) ->
'TODO'.
t_drain_deliver(_) ->
'TODO'.
t_drain_down(_) ->
'TODO'.
%% drain self() msg queue for deterministic test behavior
drain() ->
_ = drain([]), % maybe log

View File

@ -1,101 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mock_client).
-behaviour(gen_server).
-export([start_link/1, open_session/3, open_session/4,
close_session/1, stop/1, get_last_message/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {clean_start, client_id, client_pid, last_msg, session_pid}).
start_link(ClientId) ->
gen_server:start_link(?MODULE, [ClientId], []).
open_session(ClientPid, ClientId, Zone) ->
open_session(ClientPid, ClientId, Zone, _Attrs = #{}).
open_session(ClientPid, ClientId, Zone, Attrs0) ->
Attrs1 = default_session_attributes(Zone, ClientId, ClientPid),
Attrs = maps:merge(Attrs1, Attrs0),
gen_server:call(ClientPid, {start_session, ClientPid, ClientId, Attrs}).
%% close session and terminate the client itself
close_session(ClientPid) ->
gen_server:call(ClientPid, stop_session, infinity).
stop(CPid) ->
gen_server:call(CPid, stop, infinity).
get_last_message(Pid) ->
gen_server:call(Pid, get_last_message, infinity).
init([ClientId]) ->
erlang:process_flag(trap_exit, true),
{ok, #state{clean_start = true,
client_id = ClientId,
last_msg = undefined
}
}.
handle_call({start_session, ClientPid, ClientId, Attrs}, _From, State) ->
{ok, SessPid} = emqx_sm:open_session(Attrs),
{reply, {ok, SessPid},
State#state{clean_start = true,
client_id = ClientId,
client_pid = ClientPid,
session_pid = SessPid
}};
handle_call(stop_session, _From, #state{session_pid = Pid} = State) ->
is_pid(Pid) andalso is_process_alive(Pid) andalso emqx_sm:close_session(Pid),
{stop, normal, ok, State#state{session_pid = undefined}};
handle_call(get_last_message, _From, #state{last_msg = Msg} = State) ->
{reply, Msg, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({deliver, Msg}, State) ->
{noreply, State#state{last_msg = Msg}};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
default_session_attributes(Zone, ClientId, ClientPid) ->
#{zone => Zone,
client_id => ClientId,
conn_pid => ClientPid,
clean_start => true,
username => undefined,
expiry_interval => 0,
max_inflight => 0,
topic_alias_maximum => 0,
will_msg => undefined
}.

View File

@ -1,27 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mod_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
all() -> [mod_subscription_rep].
mod_subscription_rep(_) -> ok.

View File

@ -1,65 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mod_rewrite_tests).
-include_lib("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
rules() ->
Rawrules1 = "x/# ^x/y/(.+)$ z/y/$1",
Rawrules2 = "y/+/z/# ^y/(.+)/z/(.+)$ y/z/$2",
Rawrules = [Rawrules1, Rawrules2],
Rules = lists:map(fun(Rule) ->
[Topic, Re, Dest] = string:tokens(Rule, " "),
{rewrite,
list_to_binary(Topic),
list_to_binary(Re),
list_to_binary(Dest)}
end, Rawrules),
lists:map(fun({rewrite, Topic, Re, Dest}) ->
{ok, MP} = re:compile(Re),
{rewrite, Topic, MP, Dest}
end, Rules).
rewrite_subscribe_test() ->
Rules = rules(),
io:format("Rules: ~p",[Rules]),
?assertEqual({ok, [{<<"test">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"test">>, opts}], Rules)),
?assertEqual({ok, [{<<"z/y/test">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"x/y/test">>, opts}], Rules)),
?assertEqual({ok, [{<<"y/z/test_topic">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"y/test/z/test_topic">>, opts}], Rules)).
rewrite_unsubscribe_test() ->
Rules = rules(),
?assertEqual({ok, [{<<"test">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"test">>, opts}], Rules)),
?assertEqual({ok, [{<<"z/y/test">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"x/y/test">>, opts}], Rules)),
?assertEqual({ok, [{<<"y/z/test_topic">>, opts}]},
emqx_mod_rewrite:rewrite_subscribe(credentials, [{<<"y/test/z/test_topic">>, opts}], Rules)).
rewrite_publish_test() ->
Rules = rules(),
?assertMatch({ok, #message{topic = <<"test">>}},
emqx_mod_rewrite:rewrite_publish(#message{topic = <<"test">>}, Rules)),
?assertMatch({ok, #message{topic = <<"z/y/test">>}},
emqx_mod_rewrite:rewrite_publish(#message{topic = <<"x/y/test">>}, Rules)),
?assertMatch({ok, #message{topic = <<"y/z/test_topic">>}},
emqx_mod_rewrite:rewrite_publish(#message{topic = <<"y/test/z/test_topic">>}, Rules)).

View File

@ -1,45 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mod_sup_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
all() -> [t_child_all].
start_link() ->
Pid = spawn_link(?MODULE, echo, [0]),
{ok, Pid}.
echo(State) ->
receive
{From, Req} ->
ct:pal("======from:~p, req:~p", [From, Req]),
From ! Req,
echo(State)
end.
t_child_all(_) ->
{ok, Pid} = emqx_mod_sup:start_link(),
{ok, Child} = emqx_mod_sup:start_child(?MODULE, worker),
timer:sleep(10),
Child ! {self(), hi},
receive hi -> ok after 100 -> ct:fail({timeout, wait_echo}) end,
ok = emqx_mod_sup:stop_child(?MODULE),
exit(Pid, normal).

View File

@ -19,20 +19,49 @@
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
-import(emqx_mountpoint,
[ mount/2
, unmount/2
, replvar/2
]).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [t_mount_unmount, t_replvar].
all() -> emqx_ct:all(?MODULE).
t_mount_unmount(_) ->
t_mount(_) ->
Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>),
Msg2 = emqx_mountpoint:mount(<<"mount">>, Msg),
?assertEqual(<<"mounttopic">>, Msg2#message.topic),
TopicFilter = [{<<"mounttopic">>, #{qos => ?QOS_2}}],
TopicFilter = emqx_mountpoint:mount(<<"mount">>, [{<<"topic">>, #{qos => ?QOS_2}}]),
Msg = emqx_mountpoint:unmount(<<"mount">>, Msg2).
TopicFilters = [{<<"topic">>, #{qos => 2}}],
?assertEqual(<<"topic">>, mount(undefined, <<"topic">>)),
?assertEqual(Msg, mount(undefined, Msg)),
?assertEqual(TopicFilters, mount(undefined, TopicFilters)),
?assertEqual(<<"device/1/topic">>,
mount(<<"device/1/">>, <<"topic">>)),
?assertEqual(Msg#message{topic = <<"device/1/topic">>},
mount(<<"device/1/">>, Msg)),
?assertEqual([{<<"device/1/topic">>, #{qos => 2}}],
mount(<<"device/1/">>, TopicFilters)).
t_unmount(_) ->
Msg = emqx_message:make(<<"clientid">>, <<"device/1/topic">>, <<"payload">>),
?assertEqual(<<"topic">>, unmount(undefined, <<"topic">>)),
?assertEqual(Msg, unmount(undefined, Msg)),
?assertEqual(<<"topic">>, unmount(<<"device/1/">>, <<"device/1/topic">>)),
?assertEqual(Msg#message{topic = <<"topic">>}, unmount(<<"device/1/">>, Msg)),
?assertEqual(<<"device/1/topic">>, unmount(<<"device/2/">>, <<"device/1/topic">>)),
?assertEqual(Msg#message{topic = <<"device/1/topic">>}, unmount(<<"device/2/">>, Msg)).
t_replvar(_) ->
<<"mount/test/clientid">> = emqx_mountpoint:replvar(<<"mount/%u/%c">>, #{client_id => <<"clientid">>, username => <<"test">>}).
?assertEqual(undefined, replvar(undefined, #{})),
?assertEqual(<<"mount/user/clientid/">>,
replvar(<<"mount/%u/%c/">>,
#{client_id => <<"clientid">>,
username => <<"user">>
})),
?assertEqual(<<"mount/%u/clientid/">>,
replvar(<<"mount/%u/%c/">>,
#{client_id => <<"clientid">>,
username => undefined
})).

View File

@ -1,133 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mqtt_caps_SUITE).
-include_lib("eunit/include/eunit.hrl").
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
%% CT
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_get_set_caps, t_check_pub, t_check_sub].
t_get_set_caps(_) ->
{ok, _} = emqx_zone:start_link(),
Caps = #{
max_packet_size => ?MAX_PACKET_SIZE,
max_clientid_len => ?MAX_CLIENTID_LEN,
max_topic_alias => 0,
max_topic_levels => 0,
max_qos_allowed => ?QOS_2,
mqtt_retain_available => true,
mqtt_shared_subscription => true,
mqtt_wildcard_subscription => true
},
Caps2 = Caps#{max_packet_size => 1048576},
case emqx_mqtt_caps:get_caps(zone) of
Caps -> ok;
Caps2 -> ok
end,
PubCaps = #{
max_qos_allowed => ?QOS_2,
mqtt_retain_available => true,
max_topic_alias => 0
},
PubCaps = emqx_mqtt_caps:get_caps(zone, publish),
NewPubCaps = PubCaps#{max_qos_allowed => ?QOS_1},
emqx_zone:set_env(zone, '$mqtt_pub_caps', NewPubCaps),
timer:sleep(100),
NewPubCaps = emqx_mqtt_caps:get_caps(zone, publish),
SubCaps = #{
max_topic_levels => 0,
max_qos_allowed => ?QOS_2,
mqtt_shared_subscription => true,
mqtt_wildcard_subscription => true
},
SubCaps = emqx_mqtt_caps:get_caps(zone, subscribe),
emqx_zone:stop().
t_check_pub(_) ->
{ok, _} = emqx_zone:start_link(),
PubCaps = #{
max_qos_allowed => ?QOS_1,
mqtt_retain_available => false,
max_topic_alias => 4
},
emqx_zone:set_env(zone, '$mqtt_pub_caps', PubCaps),
timer:sleep(100),
ct:log("~p", [emqx_mqtt_caps:get_caps(zone, publish)]),
BadPubProps1 = #{
qos => ?QOS_2,
retain => false
},
{error, ?RC_QOS_NOT_SUPPORTED} = emqx_mqtt_caps:check_pub(zone, BadPubProps1),
BadPubProps2 = #{
qos => ?QOS_1,
retain => true
},
{error, ?RC_RETAIN_NOT_SUPPORTED} = emqx_mqtt_caps:check_pub(zone, BadPubProps2),
BadPubProps3 = #{
qos => ?QOS_1,
retain => false,
topic_alias => 5
},
{error, ?RC_TOPIC_ALIAS_INVALID} = emqx_mqtt_caps:check_pub(zone, BadPubProps3),
PubProps = #{
qos => ?QOS_1,
retain => false
},
ok = emqx_mqtt_caps:check_pub(zone, PubProps),
emqx_zone:stop().
t_check_sub(_) ->
{ok, _} = emqx_zone:start_link(),
Opts = #{qos => ?QOS_2, share => true, rc => 0},
Caps = #{
max_topic_levels => 0,
max_qos_allowed => ?QOS_2,
mqtt_shared_subscription => true,
mqtt_wildcard_subscription => true
},
ok = do_check_sub([{<<"client/stat">>, Opts}], [{<<"client/stat">>, Opts}]),
ok = do_check_sub(Caps#{max_qos_allowed => ?QOS_1}, [{<<"client/stat">>, Opts}], [{<<"client/stat">>, Opts#{qos => ?QOS_1}}]),
ok = do_check_sub(Caps#{max_topic_levels => 1},
[{<<"client/stat">>, Opts}],
[{<<"client/stat">>, Opts#{rc => ?RC_TOPIC_FILTER_INVALID}}]),
ok = do_check_sub(Caps#{mqtt_shared_subscription => false},
[{<<"client/stat">>, Opts}],
[{<<"client/stat">>, Opts#{rc => ?RC_SHARED_SUBSCRIPTIONS_NOT_SUPPORTED}}]),
ok = do_check_sub(Caps#{mqtt_wildcard_subscription => false},
[{<<"vlient/+/dsofi">>, Opts}],
[{<<"vlient/+/dsofi">>, Opts#{rc => ?RC_WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED}}]),
emqx_zone:stop().
do_check_sub(TopicFilters, Topics) ->
{ok, Topics} = emqx_mqtt_caps:check_sub(zone, TopicFilters),
ok.
do_check_sub(Caps, TopicFilters, Topics) ->
emqx_zone:set_env(zone, '$mqtt_sub_caps', Caps),
timer:sleep(100),
{_, Topics} = emqx_mqtt_caps:check_sub(zone, TopicFilters),
ok.

View File

@ -1,117 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_mqtt_packet_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-import(emqx_frame, [serialize/1]).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(INVALID_RESERVED, 1).
-define(CONNECT_INVALID_PACKET(Var),
#mqtt_packet{header = #mqtt_packet_header{type = ?INVALID_RESERVED},
variable = Var}).
-define(CASE1_PROTOCOL_NAME, ?CONNECT_PACKET(#mqtt_packet_connect{
proto_name = <<"MQTC">>,
client_id = <<"mqtt_protocol_name">>,
username = <<"admin">>,
password = <<"public">>})).
-define(CASE2_PROTOCAL_VER, ?CONNECT_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
proto_ver = 6,
username = <<"admin">>,
password = <<"public">>})).
-define(CASE3_PROTOCAL_INVALID_RESERVED, ?CONNECT_INVALID_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
proto_ver = 5,
username = <<"admin">>,
password = <<"public">>})).
-define(PROTOCOL5, ?CONNECT_PACKET(#mqtt_packet_connect{
proto_ver = 5,
keepalive = 60,
properties = #{'Message-Expiry-Interval' => 3600},
client_id = <<"mqtt_client">>,
will_topic = <<"will_tipic">>,
will_payload = <<"will message">>,
username = <<"admin">>,
password = <<"public">>})).
all() -> [{group, connect}].
groups() -> [{connect, [sequence],
[case1_protocol_name,
case2_protocol_ver%,
%TOTO case3_invalid_reserved
]}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
init_per_group(_Group, Config) ->
Config.
end_per_group(_Group, _Config) ->
ok.
case1_protocol_name(_) ->
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
MqttPacket = serialize(?CASE1_PROTOCOL_NAME),
emqx_client_sock:send(Sock, MqttPacket),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?CONNACK_PROTO_VER), <<>>, _} = raw_recv_pase(Data),
Disconnect = gen_tcp:recv(Sock, 0),
?assertEqual({error, closed}, Disconnect).
case2_protocol_ver(_) ->
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
Packet = serialize(?CASE2_PROTOCAL_VER),
emqx_client_sock:send(Sock, Packet),
{ok, Data} = gen_tcp:recv(Sock, 0),
%% case1 Unacceptable protocol version
{ok, ?CONNACK_PACKET(?CONNACK_PROTO_VER), <<>>, _} = raw_recv_pase(Data),
Disconnect = gen_tcp:recv(Sock, 0),
?assertEqual({error, closed}, Disconnect).
case3_invalid_reserved(_) ->
{ok, Sock} = emqx_client_sock:connect({127,0,0,1}, 1883, [binary, {packet, raw}, {active, false}], 3000),
Packet = serialize(?CASE3_PROTOCAL_INVALID_RESERVED),
emqx_client_sock:send(Sock, Packet),
{ok, Data} = gen_tcp:recv(Sock, 0),
%% case1 Unacceptable protocol version
ct:log("Data:~p~n", [raw_recv_pase(Data)]),
{ok, ?CONNACK_PACKET(?CONNACK_PROTO_VER), _} = raw_recv_pase(Data),
Disconnect = gen_tcp:recv(Sock, 0),
?assertEqual({error, closed}, Disconnect).
raw_recv_pase(P) ->
emqx_frame:parse(P, {none, #{max_packet_size => ?MAX_PACKET_SIZE,
version => ?MQTT_PROTO_V4} }).

View File

@ -21,9 +21,21 @@
-include("emqx_mqtt.hrl").
all() -> [t_mqtt_properties_all].
all() -> emqx_ct:all(?MODULE).
t_mqtt_properties_all(_) ->
t_id(_) ->
'TODO'.
t_name(_) ->
'TODO'.
t_filter(_) ->
'TODO'.
t_validate(_) ->
'TODO'.
deprecated_mqtt_properties_all(_) ->
Props = emqx_mqtt_props:filter(?CONNECT, #{'Session-Expiry-Interval' => 1, 'Maximum-Packet-Size' => 255}),
ok = emqx_mqtt_props:validate(Props),
#{} = emqx_mqtt_props:filter(?CONNECT, #{'Maximum-QoS' => ?QOS_2}).

View File

@ -26,8 +26,7 @@
-define(Q, emqx_mqueue).
all() -> [t_in, t_in_qos0, t_out, t_simple_mqueue, t_infinity_simple_mqueue,
t_priority_mqueue, t_infinity_priority_mqueue].
all() -> emqx_ct:all(?MODULE).
t_in(_) ->
Opts = #{max_len => 5, store_qos0 => true},
@ -130,15 +129,18 @@ t_infinity_priority_mqueue(_) ->
?assertEqual(510, ?Q:len(Qx)),
?assertEqual([{len, 510}, {max_len, 0}, {dropped, 0}], ?Q:stats(Qx)).
t_priority_mqueue2(_) ->
Opts = #{max_length => 2, store_qos0 => false},
Q = ?Q:init("priority_queue2_test", Opts),
%%TODO: fixme later
t_length_priority_mqueue(_) ->
Opts = #{max_len => 2,
store_qos0 => false
},
Q = ?Q:init(Opts),
2 = ?Q:max_len(Q),
{_, Q1} = ?Q:in(#message{topic = <<"x">>, qos = 1, payload = <<1>>}, Q),
{_, Q2} = ?Q:in(#message{topic = <<"x">>, qos = 1, payload = <<2>>}, Q1),
{_, Q3} = ?Q:in(#message{topic = <<"y">>, qos = 1, payload = <<3>>}, Q2),
{_, Q4} = ?Q:in(#message{topic = <<"y">>, qos = 1, payload = <<4>>}, Q3),
4 = ?Q:len(Q4),
?assertEqual(2, ?Q:len(Q4)),
{{value, _Val}, Q5} = ?Q:out(Q4),
3 = ?Q:len(Q5).
?assertEqual(1, ?Q:len(Q5)).

View File

@ -21,9 +21,7 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() -> [t_api].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
application:ensure_all_started(os_mon),
@ -56,3 +54,4 @@ t_api(_) ->
% timer:sleep(3000),
% ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())),
ok.

View File

@ -20,35 +20,28 @@
-compile(nowarn_export_all).
-include("emqx.hrl").
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() ->
[
packet_proto_name,
packet_type_name,
packet_validate,
packet_message,
packet_format,
packet_will_msg
].
all() -> emqx_ct:all(?MODULE).
packet_proto_name(_) ->
t_proto_name(_) ->
?assertEqual(<<"MQIsdp">>, emqx_packet:protocol_name(3)),
?assertEqual(<<"MQTT">>, emqx_packet:protocol_name(4)),
?assertEqual(<<"MQTT">>, emqx_packet:protocol_name(5)).
packet_type_name(_) ->
?assertEqual('CONNECT', emqx_packet:type_name(?CONNECT)),
t_type_name(_) ->
?assertEqual('CONNECT', emqx_packet:type_name(?CONNECT)),
?assertEqual('UNSUBSCRIBE', emqx_packet:type_name(?UNSUBSCRIBE)).
packet_validate(_) ->
?assert(emqx_packet:validate(?SUBSCRIBE_PACKET(15, #{'Subscription-Identifier' => 1}, [{<<"topic">>, #{qos => ?QOS_0}}]))),
t_validate(_) ->
?assert(emqx_packet:validate(?SUBSCRIBE_PACKET(15, #{'Subscription-Identifier' => 1},
[{<<"topic">>, #{qos => ?QOS_0}}]))),
?assert(emqx_packet:validate(?UNSUBSCRIBE_PACKET(89, [<<"topic">>]))),
?assert(emqx_packet:validate(?CONNECT_PACKET(#mqtt_packet_connect{}))),
?assert(emqx_packet:validate(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1}, <<"payload">>))),
Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1},
?assert(emqx_packet:validate(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>))),
?assert(emqx_packet:validate(?CONNECT_PACKET(#mqtt_packet_connect{properties = #{'Receive-Maximum' => 1}}))),
?assertError(subscription_identifier_invalid,
emqx_packet:validate(
@ -89,7 +82,7 @@ packet_validate(_) ->
properties =
#{'Receive-Maximum' => 0}}))).
packet_message(_) ->
t_from_to_message(_) ->
Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH,
qos = ?QOS_0,
retain = false,
@ -111,7 +104,7 @@ packet_message(_) ->
Msg5 = Msg4#message{timestamp = Msg3#message.timestamp, id = Msg3#message.id},
Msg5 = Msg3.
packet_format(_) ->
t_packet_format(_) ->
io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]),
io:format("~s", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]),
io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]),
@ -123,15 +116,17 @@ packet_format(_) ->
io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]),
io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]).
packet_will_msg(_) ->
Pkt = #mqtt_packet_connect{ will_flag = true,
client_id = <<"clientid">>,
username = "test",
will_retain = true,
will_qos = ?QOS_2,
will_topic = <<"topic">>,
will_props = #{},
will_payload = <<"payload">>},
t_will_msg(_) ->
Pkt = #mqtt_packet_connect{will_flag = true,
client_id = <<"clientid">>,
username = "test",
will_retain = true,
will_qos = ?QOS_2,
will_topic = <<"topic">>,
will_props = #{},
will_payload = <<"payload">>
},
Msg = emqx_packet:will_msg(Pkt),
?assertEqual(<<"clientid">>, Msg#message.from),
?assertEqual(<<"topic">>, Msg#message.topic).

View File

@ -21,9 +21,9 @@
-include_lib("eunit/include/eunit.hrl").
all() -> [update_counter].
all() -> emqx_ct:all(?MODULE).
update_counter(_) ->
t_update_counter(_) ->
?assertEqual(undefined, emqx_pd:update_counter(bytes, 1)),
?assertEqual(1, emqx_pd:update_counter(bytes, 1)),
?assertEqual(2, emqx_pd:update_counter(bytes, 1)),

View File

@ -21,8 +21,7 @@
-include_lib("eunit/include/eunit.hrl").
all() ->
[t_monitor, t_find, t_erase].
all() -> emqx_ct:all(?MODULE).
t_monitor(_) ->
PMon = emqx_pmon:new(),

View File

@ -19,20 +19,23 @@
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() ->
[
{group, submit_case},
{group, async_submit_case},
[{group, submit},
{group, async_submit},
t_unexpected
].
groups() ->
[
{submit_case, [sequence], [submit_mfa, submit_fa]},
{async_submit_case, [sequence], [async_submit_mfa, async_submit_crash]}
[{submit, [sequence],
[t_submit_mfa,
t_submit_fa
]},
{async_submit, [sequence],
[t_async_submit_mfa,
t_async_submit_crash
]}
].
init_per_suite(Config) ->
@ -48,22 +51,28 @@ init_per_testcase(_, Config) ->
end_per_testcase(_, Config) ->
Sup = proplists:get_value(pool_sup, Config),
%% ???
exit(Sup, normal).
submit_mfa(_Config) ->
t_submit_mfa(_Config) ->
Result = emqx_pool:submit({?MODULE, test_mfa, []}),
?assertEqual(15, Result).
submit_fa(_Config) ->
Fun = fun(X) -> case X rem 2 of 0 -> {true, X div 2}; _ -> false end end,
t_submit_fa(_Config) ->
Fun = fun(X) ->
case X rem 2 of
0 -> {true, X div 2};
_ -> false
end
end,
Result = emqx_pool:submit(Fun, [2]),
?assertEqual({true, 1}, Result).
async_submit_mfa(_Config) ->
t_async_submit_mfa(_Config) ->
emqx_pool:async_submit({?MODULE, test_mfa, []}),
emqx_pool:async_submit(fun ?MODULE:test_mfa/0, []).
async_submit_crash(_) ->
t_async_submit_crash(_) ->
emqx_pool:async_submit(fun() -> error(unexpected_error) end).
t_unexpected(_) ->

View File

@ -16,15 +16,15 @@
-module(emqx_pqueue_SUITE).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-define(PQ, emqx_pqueue).
-include_lib("eunit/include/eunit.hrl").
all() -> [t_priority_queue_plen, t_priority_queue_out2, t_priority_queues].
-define(PQ, emqx_pqueue).
-define(SUITE, ?MODULE).
all() -> emqx_ct:all(?SUITE).
t_priority_queue_plen(_) ->
Q = ?PQ:new(),
@ -87,7 +87,7 @@ t_priority_queues(_) ->
[{1, c}, {1, d}, {0, a}, {0, b}] = ?PQ:to_list(PQueue4),
PQueue4 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]),
empty = ?PQ:highest(?PQ:new()),
0 = ?PQ:highest(PQueue1),
1 = ?PQ:highest(PQueue4),
@ -122,4 +122,3 @@ t_priority_queues(_) ->
{pqueue,[{-1,{queue,[f],[d,f,d],4}},
{0,{queue,[b],[a,b,a],4}}]} = ?PQ:join(PQueue8, PQueue8).

View File

@ -1,613 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_protocol_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-include("emqx_mqtt.hrl").
-define(TOPICS, [<<"TopicA">>, <<"TopicA/B">>, <<"Topic/C">>, <<"TopicA/C">>,
<<"/TopicA">>]).
-define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"emqx">>,
password = <<"public">>})).
all() ->
[{group, mqtt_common},
{group, mqttv4},
{group, mqttv5},
{group, acl},
{group, frame_partial}].
groups() ->
[{mqtt_common, [sequence],
[will_topic_check,
will_acl_check]},
{mqttv4, [sequence],
[connect_v4,
subscribe_v4]},
{mqttv5, [sequence],
[connect_v5,
subscribe_v5]},
{acl, [sequence],
[acl_deny_action_ct]},
{frame_partial, [sequence],
[handle_followed_packet]}].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([], fun set_special_configs/1),
MqttCaps = maps:from_list(emqx_mqtt_caps:default_caps()),
emqx_zone:set_env(external, '$mqtt_caps', MqttCaps#{max_topic_alias => 20}),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
batch_connect(NumberOfConnections) ->
batch_connect([], NumberOfConnections).
batch_connect(Socks, 0) ->
Socks;
batch_connect(Socks, NumberOfConnections) ->
{ok, Sock} = emqx_client_sock:connect({127, 0, 0, 1}, 1883,
[binary, {packet, raw}, {active, false}],
3000),
batch_connect([Sock | Socks], NumberOfConnections - 1).
with_connection(DoFun, NumberOfConnections) ->
Socks = batch_connect(NumberOfConnections),
try
DoFun(Socks)
after
lists:foreach(fun(Sock) ->
emqx_client_sock:close(Sock)
end, Socks)
end.
with_connection(DoFun) ->
with_connection(DoFun, 1).
handle_followed_packet(_Config) ->
ConnPkt = <<16,12,0,4,77,81,84,84,4,2,0,60,0,0>>,
PartialPkt1 = <<50,182,1,0,4,116,101,115,116,0,1,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48>>,
PartialPkt2 = <<48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,
48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48>>,
%% This is a PUBLISH message (Qos=1)
PubPkt = <<PartialPkt1/binary, PartialPkt2/binary>>,
ComplexPkt = <<PubPkt/binary, PubPkt/binary, PubPkt/binary, PartialPkt1/binary>>,
AssertConnAck = fun(R) -> ?assertEqual({ok, <<32,2,0,0>>}, R) end,
AssertPubAck = fun(R) -> ?assertEqual({ok, <<64,2,0,1>>}, R) end,
{ok, Sock} = gen_tcp:connect("127.0.0.1", 1883, [{active, false}, binary]),
%% CONNECT
ok = gen_tcp:send(Sock, ConnPkt),
AssertConnAck(gen_tcp:recv(Sock, 4, 500)),
%% Once Publish
ok = gen_tcp:send(Sock, PubPkt),
AssertPubAck(gen_tcp:recv(Sock, 4, 500)),
%% Complex Packet
ok = gen_tcp:send(Sock, ComplexPkt),
AssertPubAck(gen_tcp:recv(Sock, 4, 500)),
AssertPubAck(gen_tcp:recv(Sock, 4, 500)),
AssertPubAck(gen_tcp:recv(Sock, 4, 500)),
ok = gen_tcp:send(Sock, PartialPkt2),
AssertPubAck(gen_tcp:recv(Sock, 4, 500)),
gen_tcp:close(Sock).
connect_v4(_) ->
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock, raw_send_serialize(?PACKET(?PUBLISH))),
{error, closed} =gen_tcp:recv(Sock, 0)
end),
with_connection(fun([Sock]) ->
ConnectPacket = raw_send_serialize(?CONNECT_PACKET
(#mqtt_packet_connect{
client_id = <<"mqttv4_client">>,
username = <<"admin">>,
password = <<"public">>,
proto_ver = ?MQTT_PROTO_V4
})),
emqx_client_sock:send(Sock, ConnectPacket),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V4),
emqx_client_sock:send(Sock, ConnectPacket),
{error, closed} = gen_tcp:recv(Sock, 0)
end),
ok.
connect_v5(_) ->
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties =
#{'Request-Response-Information' => -1}}))),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5)
end),
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties =
#{'Request-Problem-Information' => 2}}))),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5)
end),
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties =
#{'Request-Response-Information' => 1}})
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0, Props), <<>>, _} =
raw_recv_parse(Data, ?MQTT_PROTO_V5),
?assertNot(maps:is_key('Response-Information', Props))
end),
% topic alias = 0
with_connection(fun([Sock]) ->
%% ct:log("emqx_protocol: ~p~n", [emqx_zone:get_zone(external, max_topic_alias)]),
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
client_id = "hello",
proto_ver = ?MQTT_PROTO_V5,
properties =
#{'Topic-Alias-Maximum' => 10}}),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0,
#{'Topic-Alias-Maximum' := 20}), <<>>, _} =
raw_recv_parse(Data, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock,
raw_send_serialize(
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 1, #{'Topic-Alias' => 0}, <<"hello">>),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data2} = gen_tcp:recv(Sock, 0),
{ok, ?DISCONNECT_PACKET(?RC_TOPIC_ALIAS_INVALID), <<>>, _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5)
end),
% topic alias maximum
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
properties =
#{'Topic-Alias-Maximum' => 10}}),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0,
#{'Topic-Alias-Maximum' := 20}), <<>>, _} =
raw_recv_parse(Data, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1,
qos => ?QOS_2,
rap => 0,
nl => 0,
rc => 0}}]), #{version => ?MQTT_PROTO_V5})),
{ok, Data2} = gen_tcp:recv(Sock, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2]), <<>>, _} = raw_recv_parse(Data2, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock,
raw_send_serialize(
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 1, #{'Topic-Alias' => 15}, <<"hello">>),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data3} = gen_tcp:recv(Sock, 6),
{ok, ?PUBACK_PACKET(1, 0), <<>>, _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5),
{ok, Data4} = gen_tcp:recv(Sock, 0),
{ok, ?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, _, <<"hello">>), <<>>, _} = raw_recv_parse(Data4, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock,
raw_send_serialize(
?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, 2, #{'Topic-Alias' => 21}, <<"hello">>),
#{version => ?MQTT_PROTO_V5}
)),
{ok, Data5} = gen_tcp:recv(Sock, 0),
{ok, ?DISCONNECT_PACKET(?RC_TOPIC_ALIAS_INVALID), <<>>, _} = raw_recv_parse(Data5, ?MQTT_PROTO_V5)
end),
% test clean start
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = true,
client_id = <<"myclient">>,
properties =
#{'Session-Expiry-Interval' => 10}})
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock, raw_send_serialize(
?DISCONNECT_PACKET(?RC_SUCCESS)
))
end),
timer:sleep(1000),
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = false,
client_id = <<"myclient">>,
properties =
#{'Session-Expiry-Interval' => 10}})
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 1), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5)
end),
% test will message publish and cancel
with_connection(fun([Sock]) ->
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = true,
client_id = <<"myclient">>,
will_flag = true,
will_qos = ?QOS_1,
will_retain = false,
will_props = #{'Will-Delay-Interval' => 5},
will_topic = <<"TopicA">>,
will_payload = <<"will message">>,
properties = #{'Session-Expiry-Interval' => 0}
}
)
)
),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5),
{ok, Sock2} = emqx_client_sock:connect({127, 0, 0, 1}, 1883,
[binary, {packet, raw},
{active, false}], 3000),
do_connect(Sock2, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock2, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1,
qos => ?QOS_2,
rap => 0,
nl => 0,
rc => 0}}]), #{version => ?MQTT_PROTO_V5})),
{ok, SubData} = gen_tcp:recv(Sock2, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2]), <<>>, _} = raw_recv_parse(SubData, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock, raw_send_serialize(
?DISCONNECT_PACKET(?RC_SUCCESS))),
{error, timeout} = gen_tcp:recv(Sock2, 0, 2000),
% session resumed
{ok, Sock3} = emqx_client_sock:connect({127, 0, 0, 1}, 1883,
[binary, {packet, raw},
{active, false}], 3000),
emqx_client_sock:send(Sock3,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = false,
client_id = <<"myclient">>,
will_flag = true,
will_qos = ?QOS_1,
will_retain = false,
will_props = #{'Will-Delay-Interval' => 5},
will_topic = <<"TopicA">>,
will_payload = <<"will message 2">>,
properties = #{'Session-Expiry-Interval' => 3}
}
),
#{version => ?MQTT_PROTO_V5}
)
),
{ok, Data3} = gen_tcp:recv(Sock3, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), <<>>, _} = raw_recv_parse(Data3, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock3, raw_send_serialize(
?DISCONNECT_PACKET(?RC_DISCONNECT_WITH_WILL_MESSAGE),
#{version => ?MQTT_PROTO_V5}
)
),
{ok, WillData} = gen_tcp:recv(Sock2, 0, 5000),
{ok, ?PUBLISH_PACKET(?QOS_1, <<"TopicA">>, _, <<"will message 2">>), <<>>, _}
= raw_recv_parse(WillData, ?MQTT_PROTO_V5)
end),
% duplicate client id
with_connection(fun([Sock, Sock1]) ->
emqx_zone:set_env(external, use_username_as_clientid, true),
emqx_client_sock:send(Sock,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = true,
client_id = <<"myclient">>,
properties =
#{'Session-Expiry-Interval' => 10}})
)),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock1,
raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
proto_ver = ?MQTT_PROTO_V5,
clean_start = false,
client_id = <<"myclient">>,
username = <<"admin">>,
password = <<"public">>,
properties =
#{'Session-Expiry-Interval' => 10}})
)),
{ok, Data1} = gen_tcp:recv(Sock1, 0),
{ok, ?CONNACK_PACKET(?RC_SUCCESS, 0), <<>>, _} = raw_recv_parse(Data1, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1,
qos => ?QOS_2,
rap => 0,
nl => 0,
rc => 0}}]),
#{version => ?MQTT_PROTO_V5})),
{ok, SubData} = gen_tcp:recv(Sock, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2]), <<>>, _} = raw_recv_parse(SubData, ?MQTT_PROTO_V5),
emqx_client_sock:send(Sock1, raw_send_serialize(?SUBSCRIBE_PACKET(1, [{<<"TopicA">>, #{rh => 1,
qos => ?QOS_2,
rap => 0,
nl => 0,
rc => 0}}]),
#{version => ?MQTT_PROTO_V5})),
{ok, SubData1} = gen_tcp:recv(Sock1, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2]), <<>>, _} = raw_recv_parse(SubData1, ?MQTT_PROTO_V5)
end, 2),
ok.
do_connect(Sock, ProtoVer) ->
emqx_client_sock:send(Sock, raw_send_serialize(
?CONNECT_PACKET(
#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
proto_ver = ProtoVer
}))),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?CONNACK_PACKET(?CONNACK_ACCEPT), <<>>, _} = raw_recv_parse(Data, ProtoVer).
subscribe_v4(_) ->
with_connection(fun([Sock]) ->
do_connect(Sock, ?MQTT_PROTO_V4),
SubPacket = raw_send_serialize(
?SUBSCRIBE_PACKET(15,
[{<<"topic">>, #{rh => 1,
qos => ?QOS_2,
rap => 0,
nl => 0,
rc => 0}}])),
emqx_client_sock:send(Sock, SubPacket),
{ok, Data} = gen_tcp:recv(Sock, 0),
{ok, ?SUBACK_PACKET(15, _), <<>>, _} = raw_recv_parse(Data, ?MQTT_PROTO_V4)
end),
ok.
subscribe_v5(_) ->
with_connection(fun([Sock]) ->
do_connect(Sock, ?MQTT_PROTO_V5),
SubPacket = raw_send_serialize(?SUBSCRIBE_PACKET(15, #{'Subscription-Identifier' => -1},[]),
#{version => ?MQTT_PROTO_V5}),
emqx_client_sock:send(Sock, SubPacket),
{ok, DisConnData} = gen_tcp:recv(Sock, 0),
{ok, ?DISCONNECT_PACKET(?RC_TOPIC_FILTER_INVALID), <<>>, _} =
raw_recv_parse(DisConnData, ?MQTT_PROTO_V5)
end),
with_connection(fun([Sock]) ->
do_connect(Sock, ?MQTT_PROTO_V5),
SubPacket = raw_send_serialize(
?SUBSCRIBE_PACKET(0, #{}, [{<<"TopicQos0">>,
#{rh => 1, qos => ?QOS_2,
rap => 0, nl => 0,
rc => 0}}]),
#{version => ?MQTT_PROTO_V5}),
emqx_client_sock:send(Sock, SubPacket),
{ok, DisConnData} = gen_tcp:recv(Sock, 0),
?assertMatch(
{ok, ?DISCONNECT_PACKET(?RC_MALFORMED_PACKET), <<>>, _},
raw_recv_parse(DisConnData, ?MQTT_PROTO_V5))
end),
with_connection(fun([Sock]) ->
do_connect(Sock, ?MQTT_PROTO_V5),
SubPacket = raw_send_serialize(
?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 0},
[{<<"TopicQos0">>,
#{rh => 1, qos => ?QOS_2,
rap => 0, nl => 0,
rc => 0}}]),
#{version => ?MQTT_PROTO_V5}),
emqx_client_sock:send(Sock, SubPacket),
{ok, DisConnData} = gen_tcp:recv(Sock, 0),
?assertMatch(
{ok, ?DISCONNECT_PACKET(?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED), <<>>, _},
raw_recv_parse(DisConnData, ?MQTT_PROTO_V5))
end),
with_connection(fun([Sock]) ->
do_connect(Sock, ?MQTT_PROTO_V5),
SubPacket = raw_send_serialize(
?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1},
[{<<"TopicQos0">>,
#{rh => 1, qos => ?QOS_2,
rap => 0, nl => 0,
rc => 0}}]),
#{version => ?MQTT_PROTO_V5}),
emqx_client_sock:send(Sock, SubPacket),
{ok, SubData} = gen_tcp:recv(Sock, 0),
{ok, ?SUBACK_PACKET(1, #{}, [2]), <<>>, _}
= raw_recv_parse(SubData, ?MQTT_PROTO_V5)
end),
ok.
publish_v4(_) ->
ok.
publish_v5(_) ->
ok.
raw_send_serialize(Packet) ->
emqx_frame:serialize(Packet).
raw_send_serialize(Packet, Opts) ->
emqx_frame:serialize(Packet, Opts).
raw_recv_parse(Bin, ProtoVer) ->
emqx_frame:parse(Bin, emqx_frame:initial_parse_state(#{version => ProtoVer})).
acl_deny_action_ct(_) ->
emqx_zone:set_env(external, acl_deny_action, disconnect),
process_flag(trap_exit, true),
[acl_deny_do_disconnect(subscribe, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)],
[acl_deny_do_disconnect(publish, QoS, <<"acl_deny_action">>) || QoS <- lists:seq(0, 2)],
emqx_zone:set_env(external, acl_deny_action, ignore),
ok.
will_topic_check(_) ->
{ok, Client} = emqx_client:start_link([{username, <<"emqx">>},
{will_flag, true},
{will_topic, <<"aaa">>},
{will_payload, <<"I have died">>},
{will_qos, 0}]),
{ok, _} = emqx_client:connect(Client),
{ok, T} = emqx_client:start_link([{client_id, <<"client">>}]),
emqx_client:connect(T),
emqx_client:subscribe(T, <<"aaa">>),
ct:sleep(200),
emqx_client:stop(Client),
ct:sleep(100),
false = is_process_alive(Client),
emqx_ct_helpers:wait_mqtt_payload(<<"I have died">>),
emqx_client:stop(T).
will_acl_check(_) ->
%% The connection will be rejected if publishing of the will message is not allowed by
%% ACL rules
process_flag(trap_exit, true),
{ok, Client} = emqx_client:start_link([{username, <<"pub_deny">>},
{will_flag, true},
{will_topic, <<"pub_deny">>},
{will_payload, <<"I have died">>},
{will_qos, 0}]),
?assertMatch({error,{_,_}}, emqx_client:connect(Client)).
acl_deny_do_disconnect(publish, QoS, Topic) ->
process_flag(trap_exit, true),
{ok, Client} = emqx_client:start_link([{username, <<"emqx">>}]),
{ok, _} = emqx_client:connect(Client),
emqx_client:publish(Client, Topic, <<"test">>, QoS),
receive
{disconnected, shutdown, tcp_closed} ->
ct:pal(info, "[OK] after publish, client got disconnected: tcp_closed", []);
{'EXIT', Client, {shutdown,tcp_closed}} ->
ct:pal(info, "[OK] after publish, received exit: {shutdown,tcp_closed}"),
false = is_process_alive(Client);
{'EXIT', Client, Reason} ->
ct:pal(info, "[OK] after publish, client got disconnected: ~p", [Reason])
after 1000 -> ct:fail({timeout, wait_tcp_closed})
end;
acl_deny_do_disconnect(subscribe, QoS, Topic) ->
process_flag(trap_exit, true),
{ok, Client} = emqx_client:start_link([{username, <<"emqx">>}]),
{ok, _} = emqx_client:connect(Client),
{ok, _, [128]} = emqx_client:subscribe(Client, Topic, QoS),
receive
{disconnected, shutdown, tcp_closed} ->
ct:pal(info, "[OK] after subscribe, client got disconnected: tcp_closed", []);
{'EXIT', Client, {shutdown,tcp_closed}} ->
ct:pal(info, "[OK] after subscribe, received exit: {shutdown,tcp_closed}"),
false = is_process_alive(Client);
{'EXIT', Client, Reason} ->
ct:pal(info, "[OK] after subscribe, client got disconnected: ~p", [Reason])
after 1000 -> ct:fail({timeout, wait_tcp_closed})
end.
set_special_configs(emqx) ->
application:set_env(emqx, enable_acl_cache, false),
application:set_env(emqx, plugins_loaded_file,
emqx_ct_helpers:deps_path(emqx, "test/emqx_SUITE_data/loaded_plugins")),
application:set_env(emqx, acl_deny_action, disconnect),
application:set_env(emqx, acl_file,
emqx_ct_helpers:deps_path(emqx, "test/emqx_access_SUITE_data/acl_deny_action.conf"));
set_special_configs(_App) ->
ok.

View File

@ -1,30 +0,0 @@
%% Copyright (c) 2013-2019 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(emqx_protocol_tests).
-include_lib("eunit/include/eunit.hrl").
set_property_test() ->
?assertEqual(#{test => test_property}, emqx_protocol:set_property(test, test_property, undefined)),
TestMap = #{test => test_property},
?assertEqual(#{test => test_property, test1 => test_property2},
emqx_protocol:set_property(test1, test_property2, TestMap)),
ok.
init_username_test() ->
?assertEqual(<<"Peercert">>,
emqx_protocol:init_username(<<"Peercert">>, [{peer_cert_as_username, crt}])),
?assertEqual(undefined,
emqx_protocol:init_username(undefined, [{peer_cert_as_username, undefined}])).

View File

@ -14,12 +14,13 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_reason_codes_SUITE).
-module(emqx_reason_codes_tests).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-import(lists, [seq/2, zip/2, foreach/2]).
@ -89,25 +90,27 @@
?CONNACK_AUTH,
?CONNACK_SERVER, ?CONNACK_SERVER, ?CONNACK_SERVER, ?CONNACK_SERVER]).
mqttv4_name_test() ->
all() -> emqx_ct:all(?MODULE).
t_mqttv4_name() ->
(((codes_test(?MQTT_PROTO_V4))
(seq(0,6)))
(?MQTTV4_CODE_NAMES))
(fun emqx_reason_codes:name/2).
mqttv5_name_test() ->
t_mqttv5_name() ->
(((codes_test(?MQTT_PROTO_V5))
(?MQTTV5_CODES))
(?MQTTV5_CODE_NAMES))
(fun emqx_reason_codes:name/2).
text_test() ->
t_text() ->
(((codes_test(?MQTT_PROTO_V5))
(?MQTTV5_CODES))
(?MQTTV5_TXT))
(fun emqx_reason_codes:text/1).
compat_test() ->
t_compat() ->
(((codes_test(connack))
(?COMPAT_CODES_V5))
(?COMPAT_CODES_V4))
@ -135,3 +138,4 @@ codes_test(AsistVar) ->
end
end
end.

View File

@ -16,26 +16,15 @@
-module(emqx_router_SUITE).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(R, emqx_router).
all() ->
[{group, route}].
groups() ->
[{route, [sequence],
[t_mnesia,
t_add_delete,
t_do_add_delete,
t_match_routes,
t_print_routes,
t_has_routes,
t_unexpected]}].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -107,4 +96,6 @@ t_unexpected(_) ->
Router ! bad_info.
clear_tables() ->
lists:foreach(fun mnesia:clear_table/1, [emqx_route, emqx_trie, emqx_trie_node]).
lists:foreach(fun mnesia:clear_table/1,
[emqx_route, emqx_trie, emqx_trie_node]).

View File

@ -1,38 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_rpc_SUITE).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-define(MASTER, 'emqxct@127.0.0.1').
all() -> [t_rpc].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_rpc(_) ->
60000 = emqx_rpc:call(?MASTER, timer, seconds, [60]),
{badrpc, _} = emqx_rpc:call(?MASTER, os, test, []),
{_, []} = emqx_rpc:multicall([?MASTER, ?MASTER], os, timestamp, []).

View File

@ -21,20 +21,29 @@
-include_lib("eunit/include/eunit.hrl").
-import(emqx_sequence, [nextval/2, reclaim/2]).
-import(emqx_sequence,
[ nextval/2
, currval/2
, reclaim/2
]).
all() ->
[sequence_generate].
all() -> emqx_ct:all(?MODULE).
sequence_generate(_) ->
t_generate(_) ->
ok = emqx_sequence:create(seqtab),
?assertEqual(0, currval(seqtab, key)),
?assertEqual(1, nextval(seqtab, key)),
?assertEqual(1, currval(seqtab, key)),
?assertEqual(2, nextval(seqtab, key)),
?assertEqual(2, currval(seqtab, key)),
?assertEqual(3, nextval(seqtab, key)),
?assertEqual(2, reclaim(seqtab, key)),
?assertEqual(1, reclaim(seqtab, key)),
?assertEqual(0, reclaim(seqtab, key)),
?assertEqual(false, ets:member(seqtab, key)),
?assertEqual(1, nextval(seqtab, key)),
?assert(emqx_sequence:delete(seqtab)).
?assertEqual(0, reclaim(seqtab, key)),
?assertEqual(0, reclaim(seqtab, key)),
?assertEqual(false, ets:member(seqtab, key)),
?assert(emqx_sequence:delete(seqtab)),
?assertNot(emqx_sequence:delete(seqtab)).

View File

@ -21,9 +21,7 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() -> [ignore_loop, t_session_all].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -32,6 +30,42 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_info(_) ->
'TODO'.
t_attrs(_) ->
'TODO'.
t_stats(_) ->
'TODO'.
t_subscribe(_) ->
'TODO'.
t_unsubscribe(_) ->
'TODO'.
t_publish(_) ->
'TODO'.
t_puback(_) ->
'TODO'.
t_pubrec(_) ->
'TODO'.
t_pubrel(_) ->
'TODO'.
t_pubcomp(_) ->
'TODO'.
t_deliver(_) ->
'TODO'.
t_timeout(_) ->
'TODO'.
ignore_loop(_Config) ->
emqx_zone:set_env(external, ignore_loop_deliver, true),
{ok, Client} = emqx_client:start_link(),
@ -45,7 +79,7 @@ ignore_loop(_Config) ->
ok = emqx_client:disconnect(Client),
emqx_zone:set_env(external, ignore_loop_deliver, false).
t_session_all(_) ->
session_all(_) ->
emqx_zone:set_env(internal, idle_timeout, 1000),
ClientId = <<"ClientId">>,
{ok, ConnPid} = emqx_mock_client:start_link(ClientId),
@ -68,3 +102,4 @@ t_session_all(_) ->
timer:sleep(200),
[] = emqx:subscriptions(SPid),
emqx_mock_client:close_session(ConnPid).

View File

@ -1,260 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_shared_sub_SUITE).
-export([all/0, init_per_suite/1, end_per_suite/1]).
-export([t_random_basic/1,
t_random/1,
t_round_robin/1,
t_sticky/1,
t_hash/1,
t_not_so_sticky/1,
t_no_connection_nack/1
]).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(wait(For, Timeout), emqx_ct_helpers:wait_for(?FUNCTION_NAME, ?LINE, fun() -> For end, Timeout)).
all() -> [t_random_basic,
t_random,
t_round_robin,
t_sticky,
t_hash,
t_not_so_sticky,
t_no_connection_nack].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_random_basic(_) ->
ok = ensure_config(random),
ClientId = <<"ClientId">>,
{ok, ConnPid} = emqx_mock_client:start_link(ClientId),
{ok, SPid} = emqx_mock_client:open_session(ConnPid, ClientId, internal),
Message1 = emqx_message:make(<<"ClientId">>, 2, <<"foo">>, <<"hello">>),
emqx_session:subscribe(SPid, [{<<"foo">>, #{qos => 2, share => <<"group1">>}}]),
%% wait for the subscription to show up
?wait(subscribed(<<"group1">>, <<"foo">>, SPid), 1000),
PacketId = 1,
emqx_session:publish(SPid, PacketId, Message1),
?wait(case emqx_mock_client:get_last_message(ConnPid) of
[{publish, 1, _}] -> true;
Other -> Other
end, 1000),
emqx_session:pubrec(SPid, PacketId, reasoncode),
emqx_session:pubcomp(SPid, PacketId, reasoncode),
emqx_mock_client:close_session(ConnPid),
ok.
%% Start two subscribers share subscribe to "$share/g1/foo/bar"
%% Set 'sticky' dispatch strategy, send 1st message to find
%% out which member it picked, then close its connection
%% send the second message, the message should be 'nack'ed
%% by the sticky session and delivered to the 2nd session.
%% After the connection for the 2nd session is also closed,
%% i.e. when all clients are offline, the following message(s)
%% should be delivered randomly.
t_no_connection_nack(_) ->
ok = ensure_config(sticky),
Publisher = <<"publisher">>,
Subscriber1 = <<"Subscriber1">>,
Subscriber2 = <<"Subscriber2">>,
QoS = 1,
Group = <<"g1">>,
Topic = <<"foo/bar">>,
{ok, PubConnPid} = emqx_mock_client:start_link(Publisher),
{ok, SubConnPid1} = emqx_mock_client:start_link(Subscriber1),
{ok, SubConnPid2} = emqx_mock_client:start_link(Subscriber2),
%% allow session to persist after connection shutdown
Attrs = #{expiry_interval => timer:seconds(30)},
{ok, P_Pid} = emqx_mock_client:open_session(PubConnPid, Publisher, internal, Attrs),
{ok, SPid1} = emqx_mock_client:open_session(SubConnPid1, Subscriber1, internal, Attrs),
{ok, SPid2} = emqx_mock_client:open_session(SubConnPid2, Subscriber2, internal, Attrs),
emqx_session:subscribe(SPid1, [{Topic, #{qos => QoS, share => Group}}]),
emqx_session:subscribe(SPid2, [{Topic, #{qos => QoS, share => Group}}]),
%% wait for the subscriptions to show up
?wait(subscribed(Group, Topic, SPid1), 1000),
?wait(subscribed(Group, Topic, SPid2), 1000),
MkPayload = fun(PacketId) -> iolist_to_binary(["hello-", integer_to_list(PacketId)]) end,
SendF = fun(PacketId) -> emqx_session:publish(P_Pid, PacketId, emqx_message:make(Publisher, QoS, Topic, MkPayload(PacketId))) end,
SendF(1),
Ref = make_ref(),
CasePid = self(),
Received =
fun(PacketId, ConnPid) ->
Payload = MkPayload(PacketId),
case emqx_mock_client:get_last_message(ConnPid) of
[{publish, _, #message{payload = Payload}}] ->
CasePid ! {Ref, PacketId, ConnPid},
true;
_Other ->
false
end
end,
?wait(Received(1, SubConnPid1) orelse Received(1, SubConnPid2), 1000),
%% This is the connection which was picked by broker to dispatch (sticky) for 1st message
ConnPid = receive {Ref, 1, Pid} -> Pid after 1000 -> error(timeout) end,
%% Now kill the connection, expect all following messages to be delivered to the other subscriber.
emqx_mock_client:stop(ConnPid),
%% sleep then make synced calls to session processes to ensure that
%% the connection pid's 'EXIT' message is propagated to the session process
%% also to be sure sessions are still alive
timer:sleep(2),
_ = emqx_session:info(SPid1),
_ = emqx_session:info(SPid2),
%% Now we know what is the other still alive connection
[TheOtherConnPid] = [SubConnPid1, SubConnPid2] -- [ConnPid],
%% Send some more messages
PacketIdList = lists:seq(2, 10),
lists:foreach(fun(Id) ->
SendF(Id),
?wait(Received(Id, TheOtherConnPid), 1000)
end, PacketIdList),
%% Now close the 2nd (last connection)
emqx_mock_client:stop(TheOtherConnPid),
timer:sleep(2),
%% both sessions should have conn_pid = undefined
?assertEqual({conn_pid, undefined}, lists:keyfind(conn_pid, 1, emqx_session:info(SPid1))),
?assertEqual({conn_pid, undefined}, lists:keyfind(conn_pid, 1, emqx_session:info(SPid2))),
%% send more messages, but all should be queued in session state
lists:foreach(fun(Id) -> SendF(Id) end, PacketIdList),
{_, L1} = lists:keyfind(mqueue_len, 1, emqx_session:info(SPid1)),
{_, L2} = lists:keyfind(mqueue_len, 1, emqx_session:info(SPid2)),
?assertEqual(length(PacketIdList), L1 + L2),
%% clean up
emqx_mock_client:close_session(PubConnPid),
emqx_sm:close_session(SPid1),
emqx_sm:close_session(SPid2),
ok.
t_random(_) ->
test_two_messages(random).
t_round_robin(_) ->
test_two_messages(round_robin).
t_sticky(_) ->
test_two_messages(sticky).
t_hash(_) ->
test_two_messages(hash, false).
%% if the original subscriber dies, change to another one alive
t_not_so_sticky(_) ->
ok = ensure_config(sticky),
ClientId1 = <<"ClientId1">>,
ClientId2 = <<"ClientId2">>,
{ok, ConnPid1} = emqx_mock_client:start_link(ClientId1),
{ok, ConnPid2} = emqx_mock_client:start_link(ClientId2),
{ok, SPid1} = emqx_mock_client:open_session(ConnPid1, ClientId1, internal),
{ok, SPid2} = emqx_mock_client:open_session(ConnPid2, ClientId2, internal),
Message1 = emqx_message:make(ClientId1, 0, <<"foo/bar">>, <<"hello1">>),
Message2 = emqx_message:make(ClientId1, 0, <<"foo/bar">>, <<"hello2">>),
emqx_session:subscribe(SPid1, [{<<"foo/bar">>, #{qos => 0, share => <<"group1">>}}]),
%% wait for the subscription to show up
?wait(subscribed(<<"group1">>, <<"foo/bar">>, SPid1), 1000),
emqx_session:publish(SPid1, 1, Message1),
?wait(case emqx_mock_client:get_last_message(ConnPid1) of
[{publish, _, #message{payload = <<"hello1">>}}] -> true;
Other -> Other
end, 1000),
emqx_mock_client:close_session(ConnPid1),
?wait(not subscribed(<<"group1">>, <<"foo/bar">>, SPid1), 1000),
emqx_session:subscribe(SPid2, [{<<"foo/#">>, #{qos => 0, share => <<"group1">>}}]),
?wait(subscribed(<<"group1">>, <<"foo/#">>, SPid2), 1000),
emqx_session:publish(SPid2, 2, Message2),
?wait(case emqx_mock_client:get_last_message(ConnPid2) of
[{publish, _, #message{payload = <<"hello2">>}}] -> true;
Other -> Other
end, 1000),
emqx_mock_client:close_session(ConnPid2),
?wait(not subscribed(<<"group1">>, <<"foo/#">>, SPid2), 1000),
ok.
test_two_messages(Strategy) ->
test_two_messages(Strategy, _WithAck = true).
test_two_messages(Strategy, WithAck) ->
ok = ensure_config(Strategy, WithAck),
Topic = <<"foo/bar">>,
ClientId1 = <<"ClientId1">>,
ClientId2 = <<"ClientId2">>,
{ok, ConnPid1} = emqx_mock_client:start_link(ClientId1),
{ok, ConnPid2} = emqx_mock_client:start_link(ClientId2),
{ok, SPid1} = emqx_mock_client:open_session(ConnPid1, ClientId1, internal),
{ok, SPid2} = emqx_mock_client:open_session(ConnPid2, ClientId2, internal),
Message1 = emqx_message:make(ClientId1, 0, Topic, <<"hello1">>),
Message2 = emqx_message:make(ClientId1, 0, Topic, <<"hello2">>),
emqx_session:subscribe(SPid1, [{Topic, #{qos => 0, share => <<"group1">>}}]),
emqx_session:subscribe(SPid2, [{Topic, #{qos => 0, share => <<"group1">>}}]),
%% wait for the subscription to show up
?wait(subscribed(<<"group1">>, Topic, SPid1) andalso
subscribed(<<"group1">>, Topic, SPid2), 1000),
emqx_broker:publish(Message1),
Me = self(),
WaitF = fun(ExpectedPayload) ->
case last_message(ExpectedPayload, [ConnPid1, ConnPid2]) of
{true, Pid} ->
Me ! {subscriber, Pid},
true;
Other ->
Other
end
end,
?wait(WaitF(<<"hello1">>), 2000),
UsedSubPid1 = receive {subscriber, P1} -> P1 end,
emqx_broker:publish(Message2),
?wait(WaitF(<<"hello2">>), 2000),
UsedSubPid2 = receive {subscriber, P2} -> P2 end,
case Strategy of
sticky -> ?assert(UsedSubPid1 =:= UsedSubPid2);
round_robin -> ?assert(UsedSubPid1 =/= UsedSubPid2);
hash -> ?assert(UsedSubPid1 =:= UsedSubPid2);
_ -> ok
end,
emqx_mock_client:close_session(ConnPid1),
emqx_mock_client:close_session(ConnPid2),
ok.
last_message(_ExpectedPayload, []) -> <<"not yet?">>;
last_message(ExpectedPayload, [Pid | Pids]) ->
case emqx_mock_client:get_last_message(Pid) of
[{publish, _, #message{payload = ExpectedPayload}}] -> {true, Pid};
_Other -> last_message(ExpectedPayload, Pids)
end.
%%------------------------------------------------------------------------------
%% help functions
%%------------------------------------------------------------------------------
ensure_config(Strategy) ->
ensure_config(Strategy, _AckEnabled = true).
ensure_config(Strategy, AckEnabled) ->
application:set_env(emqx, shared_subscription_strategy, Strategy),
application:set_env(emqx, shared_dispatch_ack_enabled, AckEnabled),
ok.
subscribed(Group, Topic, Pid) ->
lists:member(Pid, emqx_shared_sub:subscribers(Group, Topic)).

View File

@ -1,115 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_sm_SUITE).
-include("emqx.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-compile(export_all).
-compile(nowarn_export_all).
-define(ATTRS, #{clean_start => true,
client_id => <<"client">>,
zone => internal,
username => <<"emqx">>,
expiry_interval => 0,
max_inflight => 0,
topic_alias_maximum => 0,
will_msg => undefined}).
all() -> [{group, registry}, {group, ets}].
groups() ->
Cases =
[ t_resume_session,
t_discard_session,
t_register_unregister_session,
t_get_set_session_attrs,
t_get_set_session_stats,
t_lookup_session_pids],
[ {registry, [non_parallel_tests], Cases},
{ets, [non_parallel_tests], Cases}].
init_per_suite(Config) ->
Config.
end_per_suite(_Config) ->
ok.
init_per_group(registry, Config) ->
emqx_ct_helpers:start_apps([], fun enable_session_registry/1),
Config;
init_per_group(ets, Config) ->
emqx_ct_helpers:start_apps([], fun disable_session_registry/1),
Config.
end_per_group(_, _Config) ->
emqx_ct_helpers:stop_apps([]).
init_per_testcase(_All, Config) ->
{ok, SPid} = emqx_sm:open_session(?ATTRS#{conn_pid => self()}),
[{session_pid, SPid}|Config].
end_per_testcase(_All, Config) ->
emqx_sm:close_session(?config(session_pid, Config)),
receive
{shutdown, normal} -> ok
after 500 -> ct:fail({timeout, wait_session_shutdown})
end.
enable_session_registry(_) ->
application:set_env(emqx, enable_session_registry, true),
ok.
disable_session_registry(_) ->
application:set_env(emqx, enable_session_registry, false),
ok.
t_resume_session(Config) ->
?assertEqual({ok, ?config(session_pid, Config)}, emqx_sm:resume_session(<<"client">>, ?ATTRS#{conn_pid => self()})).
t_discard_session(_) ->
?assertEqual(ok, emqx_sm:discard_session(<<"client1">>)).
t_register_unregister_session(_) ->
Pid = self(),
?assertEqual(ok, emqx_sm:register_session(<<"client">>)),
?assertEqual(ok, emqx_sm:register_session(<<"client">>, Pid)),
?assertEqual(ok, emqx_sm:unregister_session(<<"client">>)),
?assertEqual(ok, emqx_sm:unregister_session(<<"client">>), Pid).
t_get_set_session_attrs(Config) ->
SPid = ?config(session_pid, Config),
ClientPid0 = spawn(fun() -> receive _ -> ok end end),
?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, [?ATTRS#{conn_pid => ClientPid0}])),
?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, SPid, [?ATTRS#{conn_pid => ClientPid0}])),
[SAttr0] = emqx_sm:get_session_attrs(<<"client">>, SPid),
?assertEqual(ClientPid0, maps:get(conn_pid, SAttr0)),
?assertEqual(true, emqx_sm:set_session_attrs(<<"client">>, SPid, [?ATTRS#{conn_pid => self()}])),
[SAttr1] = emqx_sm:get_session_attrs(<<"client">>, SPid),
?assertEqual(self(), maps:get(conn_pid, SAttr1)).
t_get_set_session_stats(Config) ->
SPid = ?config(session_pid, Config),
?assertEqual(true, emqx_sm:set_session_stats(<<"client">>, [{inflight, 10}])),
?assertEqual(true, emqx_sm:set_session_stats(<<"client">>, SPid, [{inflight, 10}])),
?assertEqual([{inflight, 10}], emqx_sm:get_session_stats(<<"client">>, SPid)).
t_lookup_session_pids(Config) ->
SPid = ?config(session_pid, Config),
?assertEqual([SPid], emqx_sm:lookup_session_pids(<<"client">>)).

View File

@ -14,36 +14,41 @@
%% limitations under the License.
%%--------------------------------------------------------------------
-module(emqx_stats_tests).
-module(emqx_stats_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
get_state_test() ->
all() -> emqx_ct:all(?MODULE).
t_get_state() ->
with_proc(fun() ->
SetConnsCount = emqx_stats:statsfun('connections.count'),
SetConnsCount(1),
1 = emqx_stats:getstat('connections.count'),
?assertEqual(1, emqx_stats:getstat('connections.count')),
emqx_stats:setstat('connections.count', 2),
2 = emqx_stats:getstat('connections.count'),
?assertEqual(2, emqx_stats:getstat('connections.count')),
emqx_stats:setstat('connections.count', 'connections.max', 3),
timer:sleep(100),
3 = emqx_stats:getstat('connections.count'),
3 = emqx_stats:getstat('connections.max'),
?assertEqual(3, emqx_stats:getstat('connections.count')),
?assertEqual(3, emqx_stats:getstat('connections.max')),
emqx_stats:setstat('connections.count', 'connections.max', 2),
timer:sleep(100),
2 = emqx_stats:getstat('connections.count'),
3 = emqx_stats:getstat('connections.max'),
?assertEqual(2, emqx_stats:getstat('connections.count')),
?assertEqual(3, emqx_stats:getstat('connections.max')),
SetConns = emqx_stats:statsfun('connections.count', 'connections.max'),
SetConns(4),
timer:sleep(100),
4 = emqx_stats:getstat('connections.count'),
4 = emqx_stats:getstat('connections.max'),
?assertEqual(4, emqx_stats:getstat('connections.count')),
?assertEqual(4, emqx_stats:getstat('connections.max')),
Conns = emqx_stats:getstats(),
4 = proplists:get_value('connections.count', Conns),
4 = proplists:get_value('connections.max', Conns)
?assertEqual(4, proplists:get_value('connections.count', Conns)),
?assertEqual(4, proplists:get_value('connections.max', Conns))
end).
update_interval_test() ->
t_update_interval() ->
TickMs = 200,
with_proc(fun() ->
SleepMs = TickMs * 2 + TickMs div 2, %% sleep for 2.5 ticks

View File

@ -19,21 +19,27 @@
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(SYSMON, emqx_sys_mon).
-include("emqx_mqtt.hrl").
-define(SYSMONPID, emqx_sys_mon).
-define(INPUTINFO, [{self(), long_gc, concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), long_schedule, concat_str("long_schedule warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), busy_port, concat_str("busy_port warning: suspid = ~p, port = ~p", self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")},
{self(), busy_dist_port, concat_str("busy_dist_port warning: suspid = ~p, port = ~p", self(), list_to_port("#Port<0.4>")),list_to_port("#Port<0.4>")},
{list_to_port("#Port<0.4>"), long_schedule, concat_str("long_schedule warning: port = ~p, info: ~p", list_to_port("#Port<0.4>"), "hello"), "hello"}
-define(INPUTINFO, [{self(), long_gc,
concat_str("long_gc warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), long_schedule,
concat_str("long_schedule warning: pid = ~p, info: ~p", self(), "hello"), "hello"},
{self(), busy_port,
concat_str("busy_port warning: suspid = ~p, port = ~p",
self(), list_to_port("#Port<0.4>")), list_to_port("#Port<0.4>")},
{self(), busy_dist_port,
concat_str("busy_dist_port warning: suspid = ~p, port = ~p",
self(), list_to_port("#Port<0.4>")),list_to_port("#Port<0.4>")},
{list_to_port("#Port<0.4>"), long_schedule,
concat_str("long_schedule warning: port = ~p, info: ~p",
list_to_port("#Port<0.4>"), "hello"), "hello"}
]).
all() -> [t_sys_mon].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -43,16 +49,17 @@ end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_sys_mon(_Config) ->
lists:foreach(fun({PidOrPort, SysMonName,ValidateInfo, InfoOrPort}) ->
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort)
end, ?INPUTINFO).
lists:foreach(
fun({PidOrPort, SysMonName,ValidateInfo, InfoOrPort}) ->
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort)
end, ?INPUTINFO).
validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) ->
{ok, C} = emqx_client:start_link([{host, "localhost"}]),
{ok, _} = emqx_client:connect(C),
emqx_client:subscribe(C, emqx_topic:systop(lists:concat(['sysmon/', SysMonName])), qos1),
timer:sleep(100),
?SYSMONPID ! {monitor, PidOrPort, SysMonName, InfoOrPort},
?SYSMON ! {monitor, PidOrPort, SysMonName, InfoOrPort},
receive
{publish, #{payload := Info}} ->
?assertEqual(ValidateInfo, binary_to_list(Info)),

View File

@ -19,12 +19,27 @@
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_new].
-include_lib("eunit/include/eunit.hrl").
-define(TAB, ?MODULE).
all() -> emqx_ct:all(?MODULE).
t_new(_) ->
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
ets:insert(test_table, {key, 100}),
ok = emqx_tables:new(test_table, [{read_concurrency, true}]),
100 = ets:lookup_element(test_table, key, 2),
ok = emqx_tables:delete(test_table),
ok = emqx_tables:delete(test_table).
ok = emqx_tables:new(?TAB),
ok = emqx_tables:new(?TAB, [{read_concurrency, true}]),
?assertEqual(?TAB, ets:info(?TAB, name)).
t_lookup_value(_) ->
ok = emqx_tables:new(?TAB, []),
true = ets:insert(?TAB, {key, val}),
?assertEqual(val, emqx_tables:lookup_value(?TAB, key)),
?assertEqual(undefined, emqx_tables:lookup_value(?TAB, badkey)).
t_delete(_) ->
ok = emqx_tables:new(?TAB, []),
?assertEqual(?TAB, ets:info(?TAB, name)),
ok = emqx_tables:delete(?TAB),
ok = emqx_tables:delete(?TAB),
?assertEqual(undefined, ets:info(?TAB, name)).

View File

@ -16,14 +16,19 @@
-module(emqx_time_SUITE).
-include_lib("eunit/include/eunit.hrl").
-compile(export_all).
-compile(nowarn_export_all).
all() -> [t_time_now_to].
-include_lib("eunit/include/eunit.hrl").
all() -> emqx_ct:all(?MODULE).
t_seed(_) ->
?assert(is_tuple(emqx_time:seed())).
t_now_secs(_) ->
?assert(emqx_time:now_secs() =< emqx_time:now_secs(os:timestamp())).
t_now_ms(_) ->
?assert(emqx_time:now_ms() =< emqx_time:now_ms(os:timestamp())).
t_time_now_to(_) ->
emqx_time:seed(),
emqx_time:now_secs(),
emqx_time:now_ms().

View File

@ -16,44 +16,29 @@
-module(emqx_topic_SUITE).
-include_lib("eunit/include/eunit.hrl").
%% CT
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("emqx_ct_helpers/include/emqx_ct.hrl").
-import(emqx_topic,
[ wildcard/1
, match/2
, validate/1
, triples/1
, prepend/2
, join/1
, words/1
, systop/1
, feed_var/3
, parse/1
, parse/2
]).
-define(N, 10000).
-define(N, 100000).
all() ->
[t_wildcard,
t_match, t_match2, t_match3,
t_validate,
t_triples,
t_join,
t_levels,
t_tokens,
t_words,
t_systop,
t_feed_var,
t_sys_match,
't_#_match',
t_sigle_level_validate,
t_sigle_level_match,
t_match_perf,
t_triples_perf,
t_parse].
all() -> emqx_ct:all(?MODULE).
t_wildcard(_) ->
true = wildcard(<<"a/b/#">>),
@ -61,7 +46,7 @@ t_wildcard(_) ->
false = wildcard(<<"">>),
false = wildcard(<<"a/b/c">>).
t_match(_) ->
t_match1(_) ->
true = match(<<"a/b/c">>, <<"a/b/+">>),
true = match(<<"a/b/c">>, <<"a/#">>),
true = match(<<"abcd/ef/g">>, <<"#">>),
@ -132,74 +117,74 @@ t_match_perf(_) ->
Name = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>,
Filter = <<"/abkc/19383/+/akakdkkdkak/#">>,
true = match(Name, Filter),
{Time, _} = timer:tc(fun() ->
[match(Name, Filter) || _I <- lists:seq(1, ?N)]
end),
io:format("Time for match: ~p(micro)", [Time/?N]).
ok = bench('match/2', fun emqx_topic:match/2, [Name, Filter]).
t_validate(_) ->
true = validate({name, <<"abc/de/f">>}),
true = validate({filter, <<"abc/+/f">>}),
true = validate({filter, <<"abc/#">>}),
true = validate({filter, <<"x">>}),
true = validate({name, <<"x//y">>}),
true = validate({filter, <<"sport/tennis/#">>}),
catch validate({name, <<>>}),
catch validate({name, long_topic()}),
catch validate({name, <<"abc/#">>}),
catch validate({filter, <<"abc/#/1">>}),
catch validate({filter, <<"abc/#xzy/+">>}),
catch validate({filter, <<"abc/xzy/+9827">>}),
catch validate({filter, <<"sport/tennis#">>}),
catch validate({filter, <<"sport/tennis/#/ranking">>}),
ok.
true = validate(<<"a/+/#">>),
true = validate(<<"a/b/c/d">>),
true = validate({name, <<"abc/de/f">>}),
true = validate({filter, <<"abc/+/f">>}),
true = validate({filter, <<"abc/#">>}),
true = validate({filter, <<"x">>}),
true = validate({name, <<"x//y">>}),
true = validate({filter, <<"sport/tennis/#">>}),
ok = ?catch_error(empty_topic, validate({name, <<>>})),
ok = ?catch_error(topic_name_error, validate({name, <<"abc/#">>})),
ok = ?catch_error(topic_too_long, validate({name, long_topic()})),
ok = ?catch_error('topic_invalid_#', validate({filter, <<"abc/#/1">>})),
ok = ?catch_error(topic_invalid_char, validate({filter, <<"abc/#xzy/+">>})),
ok = ?catch_error(topic_invalid_char, validate({filter, <<"abc/xzy/+9827">>})),
ok = ?catch_error(topic_invalid_char, validate({filter, <<"sport/tennis#">>})),
ok = ?catch_error('topic_invalid_#', validate({filter, <<"sport/tennis/#/ranking">>})).
t_sigle_level_validate(_) ->
true = validate({filter, <<"+">>}),
true = validate({filter, <<"+/tennis/#">>}),
true = validate({filter, <<"sport/+/player1">>}),
catch validate({filter, <<"sport+">>}),
ok.
true = validate({filter, <<"+">>}),
true = validate({filter, <<"+/tennis/#">>}),
true = validate({filter, <<"sport/+/player1">>}),
ok = ?catch_error(topic_invalid_char, validate({filter, <<"sport+">>})).
t_triples(_) ->
Triples = [{root,<<"a">>,<<"a">>},
{<<"a">>,<<"b">>,<<"a/b">>},
{<<"a/b">>,<<"c">>,<<"a/b/c">>}],
Triples = triples(<<"a/b/c">>).
?assertEqual(Triples, triples(<<"a/b/c">>)).
t_triples_perf(_) ->
Topic = <<"/abkc/19383/192939/akakdkkdkak/xxxyyuya/akakak">>,
{Time, _} = timer:tc(fun() ->
[triples(Topic) || _I <- lists:seq(1, ?N)]
end),
io:format("Time for triples: ~p(micro)", [Time/?N]).
ok = bench('triples/1', fun emqx_topic:triples/1, [Topic]).
t_prepend(_) ->
?assertEqual(<<"a/b/c">>, prepend(root, <<"a/b/c">>)),
?assertEqual(<<"ab">>, prepend(undefined, <<"ab">>)),
?assertEqual(<<"a/b">>, prepend(<<>>, <<"a/b">>)),
?assertEqual(<<"x/a/b">>, prepend("x/", <<"a/b">>)),
?assertEqual(<<"x/y/a/b">>, prepend(<<"x/y">>, <<"a/b">>)),
?assertEqual(<<"+/a/b">>, prepend('+', <<"a/b">>)).
t_levels(_) ->
?assertEqual(3, emqx_topic:levels(<<"a/+/#">>)),
?assertEqual(4, emqx_topic:levels(<<"a/b/c/d">>)).
t_tokens(_) ->
?assertEqual([<<"a">>, <<"b">>, <<"+">>, <<"#">>], emqx_topic:tokens(<<"a/b/+/#">>)).
?assertEqual([<<"a">>, <<"b">>, <<"+">>, <<"#">>],
emqx_topic:tokens(<<"a/b/+/#">>)).
t_words(_) ->
['', <<"a">>, '+', '#'] = words(<<"/a/+/#">>),
['', <<"abkc">>, <<"19383">>, '+', <<"akakdkkdkak">>, '#'] = words(<<"/abkc/19383/+/akakdkkdkak/#">>),
{Time, _} = timer:tc(fun() ->
[words(<<"/abkc/19383/+/akakdkkdkak/#">>) || _I <- lists:seq(1, ?N)]
end),
io:format("Time for words: ~p(micro)", [Time/?N]),
{Time2, _} = timer:tc(fun() ->
[binary:split(<<"/abkc/19383/+/akakdkkdkak/#">>, <<"/">>, [global]) || _I <- lists:seq(1, ?N)]
end),
io:format("Time for binary:split: ~p(micro)", [Time2/?N]).
Topic = <<"/abkc/19383/+/akakdkkdkak/#">>,
?assertEqual(['', <<"a">>, '+', '#'], words(<<"/a/+/#">>)),
?assertEqual(['', <<"abkc">>, <<"19383">>, '+', <<"akakdkkdkak">>, '#'], words(Topic)),
ok = bench('words/1', fun emqx_topic:words/1, [Topic]),
BSplit = fun(Bin) -> binary:split(Bin, <<"/">>, [global]) end,
ok = bench('binary:split/3', BSplit, [Topic]).
t_join(_) ->
<<>> = join([]),
<<"x">> = join([<<"x">>]),
<<"#">> = join(['#']),
<<"+//#">> = join(['+', '', '#']),
<<"x/y/z/+">> = join([<<"x">>, <<"y">>, <<"z">>, '+']),
<<"/ab/cd/ef/">> = join(words(<<"/ab/cd/ef/">>)),
<<"ab/+/#">> = join(words(<<"ab/+/#">>)).
?assertEqual(<<>>, join([])),
?assertEqual(<<"x">>, join([<<"x">>])),
?assertEqual(<<"#">>, join(['#'])),
?assertEqual(<<"+//#">>, join(['+', '', '#'])),
?assertEqual(<<"x/y/z/+">>, join([<<"x">>, <<"y">>, <<"z">>, '+'])),
?assertEqual(<<"/ab/cd/ef/">>, join(words(<<"/ab/cd/ef/">>))),
?assertEqual(<<"ab/+/#">>, join(words(<<"ab/+/#">>))).
t_systop(_) ->
SysTop1 = iolist_to_binary(["$SYS/brokers/", atom_to_list(node()), "/xyz"]),
@ -219,12 +204,29 @@ long_topic() ->
iolist_to_binary([[integer_to_list(I), "/"] || I <- lists:seq(0, 10000)]).
t_parse(_) ->
ok = ?catch_error({invalid_topic_filter, <<"$queue/t">>},
parse(<<"$queue/t">>, #{share => <<"g">>})),
ok = ?catch_error({invalid_topic_filter, <<"$share/g/t">>},
parse(<<"$share/g/t">>, #{share => <<"g">>})),
ok = ?catch_error({invalid_topic_filter, <<"$share/t">>},
parse(<<"$share/t">>)),
ok = ?catch_error({invalid_topic_filter, <<"$share/+/t">>},
parse(<<"$share/+/t">>)),
?assertEqual({<<"a/b/+/#">>, #{}}, parse(<<"a/b/+/#">>)),
?assertEqual({<<"a/b/+/#">>, #{qos => 1}}, parse({<<"a/b/+/#">>, #{qos => 1}})),
?assertEqual({<<"topic">>, #{ share => <<"$queue">> }}, parse(<<"$queue/topic">>)),
?assertEqual({<<"topic">>, #{ share => <<"group">>}}, parse(<<"$share/group/topic">>)),
?assertEqual({<<"topic">>, #{share => <<"$queue">>}}, parse(<<"$queue/topic">>)),
?assertEqual({<<"topic">>, #{share => <<"group">>}}, parse(<<"$share/group/topic">>)),
%% The '$local' and '$fastlane' topics have been deprecated.
?assertEqual({<<"$local/topic">>, #{}}, parse(<<"$local/topic">>)),
?assertEqual({<<"$local/$queue/topic">>, #{}}, parse(<<"$local/$queue/topic">>)),
?assertEqual({<<"$local/$share/group/a/b/c">>, #{}}, parse(<<"$local/$share/group/a/b/c">>)),
?assertEqual({<<"$fastlane/topic">>, #{}}, parse(<<"$fastlane/topic">>)).
bench(Case, Fun, Args) ->
{Time, ok} = timer:tc(fun lists:foreach/2,
[fun(_) -> apply(Fun, Args) end,
lists:seq(1, ?N)
]),
ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w",
[Case, Time/?N, Case, (?N * 1000000) div Time]).

View File

@ -20,10 +20,9 @@
-compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
all() -> [start_traces].
all() -> [t_start_traces].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
@ -32,7 +31,7 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
start_traces(_Config) ->
t_start_traces(_Config) ->
{ok, T} = emqx_client:start_link([{host, "localhost"},
{client_id, <<"client">>},
{username, <<"testuser">>},

View File

@ -25,8 +25,7 @@
-define(TRIE, emqx_trie).
-define(TRIE_TABS, [emqx_trie, emqx_trie_node]).
all() ->
[t_mnesia, t_insert, t_match, t_match2, t_match3, t_empty, t_delete, t_delete2, t_delete3].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
application:load(emqx),
@ -51,7 +50,8 @@ t_insert(_) ->
TN = #trie_node{node_id = <<"sensor">>,
edge_count = 3,
topic = <<"sensor">>,
flags = undefined},
flags = undefined
},
Fun = fun() ->
?TRIE:insert(<<"sensor/1/metric/2">>),
?TRIE:insert(<<"sensor/+/#">>),

View File

@ -19,7 +19,7 @@
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
-define(SYSTEM_INFO, [allocated_areas,
allocator,
@ -94,82 +94,79 @@
min_heap_size]).
%fullsweep_after]).
all() -> emqx_ct:all(?MODULE).
t_load(_Config) ->
?assertMatch([{load1, _},
{load5, _},
{load15, _}
], emqx_vm:loads()).
all() ->
[load, systeminfo, mem_info, process_list, process_info, process_gc,
get_ets_list, get_ets_info, get_ets_object, get_port_types, get_port_info,
scheduler_usage, get_memory, microsecs, schedulers, get_process_group_leader_info,
get_process_limit].
load(_Config) ->
Loads = emqx_vm:loads(),
[{load1, _}, {load5, _}, {load15, _}] = Loads.
systeminfo(_Config) ->
t_systeminfo(_Config) ->
Keys = [Key || {Key, _} <- emqx_vm:get_system_info()],
?SYSTEM_INFO = Keys.
mem_info(_Config) ->
t_mem_info(_Config) ->
application:ensure_all_started(os_mon),
MemInfo = emqx_vm:mem_info(),
[{total_memory, _},
{used_memory, _}]= MemInfo,
application:stop(os_mon).
process_list(_Config) ->
t_process_list(_Config) ->
Pid = self(),
ProcessInfo = emqx_vm:get_process_list(),
true = lists:member({pid, Pid}, lists:concat(ProcessInfo)).
process_info(_Config) ->
t_process_info(_Config) ->
ProcessInfos = emqx_vm:get_process_info(),
ProcessInfo = lists:last(ProcessInfos),
Keys = [K || {K, _V}<- ProcessInfo],
?PROCESS_INFO = Keys.
process_gc(_Config) ->
t_process_gc(_Config) ->
ProcessGcs = emqx_vm:get_process_gc(),
ProcessGc = lists:last(ProcessGcs),
Keys = [K || {K, _V}<- ProcessGc],
?PROCESS_GC = Keys.
get_ets_list(_Config) ->
t_get_ets_list(_Config) ->
ets:new(test, [named_table]),
Ets = emqx_vm:get_ets_list(),
true = lists:member(test, Ets).
get_ets_info(_Config) ->
t_get_ets_info(_Config) ->
ets:new(test, [named_table]),
[] = emqx_vm:get_ets_info(test1),
EtsInfo = emqx_vm:get_ets_info(test),
test = proplists:get_value(name, EtsInfo).
get_ets_object(_Config) ->
t_get_ets_object(_Config) ->
ets:new(test, [named_table]),
ets:insert(test, {k, v}),
[{k, v}] = emqx_vm:get_ets_object(test).
get_port_types(_Config) ->
t_get_port_types(_Config) ->
emqx_vm:get_port_types().
get_port_info(_Config) ->
t_get_port_info(_Config) ->
emqx_vm:get_port_info().
scheduler_usage(_Config) ->
t_scheduler_usage(_Config) ->
emqx_vm:scheduler_usage(5000).
get_memory(_Config) ->
t_get_memory(_Config) ->
emqx_vm:get_memory().
microsecs(_Config) ->
t_microsecs(_Config) ->
emqx_vm:microsecs().
schedulers(_Config) ->
t_schedulers(_Config) ->
emqx_vm:schedulers().
get_process_group_leader_info(_Config) ->
t_get_process_group_leader_info(_Config) ->
emqx_vm:get_process_group_leader_info(self()).
get_process_limit(_Config) ->
t_get_process_limit(_Config) ->
emqx_vm:get_process_limit().

View File

@ -21,8 +21,6 @@
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(WAIT(PATTERN, TIMEOUT),
receive
PATTERN ->
@ -32,7 +30,7 @@
error(timeout)
end).
all() -> [t_api].
all() -> emqx_ct:all(?MODULE).
init_per_suite(Config) ->
application:ensure_all_started(sasl),
@ -75,3 +73,4 @@ t_api(_) ->
after
meck:unload(alarm_handler)
end.

View File

@ -1,144 +0,0 @@
%%--------------------------------------------------------------------
%% Copyright (c) 2019 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(emqx_ws_channel_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-define(CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"admin">>,
password = <<"public">>})).
-define(WILL_TOPIC, <<"test/websocket/will">>).
-define(WILL_CLIENT, ?CONNECT_PACKET(#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"admin">>,
password = <<"public">>,
will_flag = true,
will_qos = ?QOS_1,
will_topic = ?WILL_TOPIC,
will_payload = <<"payload">>
})).
all() ->
[ t_ws_connect_api
, t_ws_auth_failure
, t_ws_other_type_frame
, t_ws_will
].
init_per_suite(Config) ->
emqx_ct_helpers:start_apps([]),
Config.
end_per_suite(_Config) ->
emqx_ct_helpers:stop_apps([]).
t_ws_will(_Config) ->
{ok, ClientPid} = emqx_client:start_link(),
{ok, _} = emqx_client:connect(ClientPid),
{ok, _, [1]} = emqx_client:subscribe(ClientPid, ?WILL_TOPIC, qos1),
WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()),
{ok, _} = rfc6455_client:open(WS),
Packet = raw_send_serialize(?WILL_CLIENT),
ok = rfc6455_client:send_binary(WS, Packet),
{binary, Bin} = rfc6455_client:recv(WS),
Connack = ?CONNACK_PACKET(?CONNACK_ACCEPT),
{ok, Connack, <<>>, _} = raw_recv_pase(Bin),
exit(WS, abnomal),
?assertEqual(1, length(emqx_client_SUITE:receive_messages(1))),
ok = emqx_client:disconnect(ClientPid),
ok.
t_ws_auth_failure(_Config) ->
application:set_env(emqx, allow_anonymous, false),
WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()),
{ok, _} = rfc6455_client:open(WS),
Connect = ?CONNECT_PACKET(
#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"admin">>,
password = <<"public">>
}),
ok = rfc6455_client:send_binary(WS, raw_send_serialize(Connect)),
{binary, Bin} = rfc6455_client:recv(WS),
Connack = ?CONNACK_PACKET(?CONNACK_ACCEPT),
{ok, Connack, <<>>, _} = raw_recv_pase(Bin),
Pid = emqx_cm:lookup_conn_pid(<<"mqtt_client">>),
ConnInfo = emqx_ws_channel:info(Pid),
ok = t_info(ConnInfo),
ConnAttrs = emqx_ws_channel:attrs(Pid),
ok = t_attrs(ConnAttrs),
ConnStats = emqx_ws_channel:stats(Pid),
ok = t_stats(ConnStats),
SessionPid = emqx_ws_channel:session(Pid),
true = is_pid(SessionPid),
ok = emqx_ws_channel:kick(Pid),
{close, _} = rfc6455_client:close(WS),
ok.
t_ws_other_type_frame(_Config) ->
WS = rfc6455_client:new("ws://127.0.0.1:8083" ++ "/mqtt", self()),
{ok, _} = rfc6455_client:open(WS),
Connect = ?CONNECT_PACKET(
#mqtt_packet_connect{
client_id = <<"mqtt_client">>,
username = <<"admin">>,
password = <<"public">>
}),
ok = rfc6455_client:send_binary(WS, raw_send_serialize(Connect)),
{binary, Bin} = rfc6455_client:recv(WS),
Connack = ?CONNACK_PACKET(?CONNACK_ACCEPT),
{ok, Connack, <<>>, _} = raw_recv_pase(Bin),
rfc6455_client:send(WS, <<"testdata">>),
timer:sleep(1000),
?assertEqual(undefined, erlang:process_info(WS)),
ok.
raw_send_serialize(Packet) ->
emqx_frame:serialize(Packet).
raw_recv_pase(Packet) ->
emqx_frame:parse(Packet).
t_info(InfoData) ->
?assertEqual(websocket, maps:get(socktype, InfoData)),
?assertEqual(running, maps:get(conn_state, InfoData)),
?assertEqual(<<"mqtt_client">>, maps:get(client_id, InfoData)),
?assertEqual(<<"admin">>, maps:get(username, InfoData)),
?assertEqual(<<"MQTT">>, maps:get(proto_name, InfoData)).
t_attrs(AttrsData) ->
?assertEqual(<<"mqtt_client">>, maps:get(client_id, AttrsData)),
?assertEqual(emqx_ws_channel, maps:get(conn_mod, AttrsData)),
?assertEqual(<<"admin">>, maps:get(username, AttrsData)).
t_stats(StatsData) ->
?assertEqual(true, proplists:get_value(recv_oct, StatsData) >= 0),
?assertEqual(true, proplists:get_value(mailbox_len, StatsData) >= 0),
?assertEqual(true, proplists:get_value(heap_size, StatsData) >= 0),
?assertEqual(true, proplists:get_value(reductions, StatsData) >=0),
?assertEqual(true, proplists:get_value(recv_pkt, StatsData) =:=1),
?assertEqual(true, proplists:get_value(recv_msg, StatsData) >=0),
?assertEqual(true, proplists:get_value(send_pkt, StatsData) =:=1).

View File

@ -19,20 +19,31 @@
-compile(export_all).
-compile(nowarn_export_all).
-include("emqx_mqtt.hrl").
-include_lib("eunit/include/eunit.hrl").
all() -> [t_set_get_env].
-define(OPTS, [{enable_acl, true},
{enable_banned, false}
]).
all() -> emqx_ct:all(?MODULE).
t_set_get_env(_) ->
application:set_env(emqx, zones, [{china, [{language, chinese}]}]),
_ = application:load(emqx),
application:set_env(emqx, zones, [{external, ?OPTS}]),
{ok, _} = emqx_zone:start_link(),
chinese = emqx_zone:get_env(china, language),
cn470 = emqx_zone:get_env(china, ism_band, cn470),
undefined = emqx_zone:get_env(undefined, delay),
500 = emqx_zone:get_env(undefined, delay, 500),
application:set_env(emqx, zones, [{zone1, [{key, val}]}]),
?assertEqual(undefined, emqx_zone:get_env(zone1, key)),
emqx_zone:force_reload(),
?assertEqual(val, emqx_zone:get_env(zone1, key)),
?assert(emqx_zone:get_env(external, enable_acl)),
?assertNot(emqx_zone:get_env(external, enable_banned)),
?assertEqual(defval, emqx_zone:get_env(extenal, key, defval)),
?assertEqual(undefined, emqx_zone:get_env(external, key)),
?assertEqual(undefined, emqx_zone:get_env(internal, key)),
?assertEqual(def, emqx_zone:get_env(internal, key, def)),
emqx_zone:stop().
t_force_reload(_) ->
{ok, _} = emqx_zone:start_link(),
application:set_env(emqx, zones, [{zone, [{key, val}]}]),
?assertEqual(undefined, emqx_zone:get_env(zone, key)),
ok = emqx_zone:force_reload(),
?assertEqual(val, emqx_zone:get_env(zone, key)),
emqx_zone:stop().

View File

@ -1,251 +0,0 @@
%% The contents of this file are subject to the Mozilla Public License
%% Version 1.1 (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.mozilla.org/MPL/
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
%% License for the specific language governing rights and limitations
%% under the License.
%%
%% The Original Code is RabbitMQ Management Console.
%%
%% The Initial Developer of the Original Code is GoPivotal, Inc.
%% Copyright (c) 2012-2016 Pivotal Software, Inc. All rights reserved.
%%
-module(rfc6455_client).
-export([new/2, open/1, recv/1, send/2, send_binary/2, close/1, close/2]).
-record(state, {host, port, addr, path, ppid, socket, data, phase}).
%% --------------------------------------------------------------------------
new(WsUrl, PPid) ->
crypto:start(),
"ws://" ++ Rest = WsUrl,
[Addr, Path] = split("/", Rest, 1),
[Host, MaybePort] = split(":", Addr, 1, empty),
Port = case MaybePort of
empty -> 80;
V -> {I, ""} = string:to_integer(V), I
end,
State = #state{host = Host,
port = Port,
addr = Addr,
path = "/" ++ Path,
ppid = PPid},
spawn(fun() ->
start_conn(State)
end).
open(WS) ->
receive
{rfc6455, open, WS, Opts} ->
{ok, Opts};
{rfc6455, close, WS, R} ->
{close, R}
end.
recv(WS) ->
receive
{rfc6455, recv, WS, Payload} ->
{ok, Payload};
{rfc6455, recv_binary, WS, Payload} ->
{binary, Payload};
{rfc6455, close, WS, R} ->
{close, R}
end.
send(WS, IoData) ->
WS ! {send, IoData},
ok.
send_binary(WS, IoData) ->
WS ! {send_binary, IoData},
ok.
close(WS) ->
close(WS, {1000, ""}).
close(WS, WsReason) ->
WS ! {close, WsReason},
receive
{rfc6455, close, WS, R} ->
{close, R}
end.
%% --------------------------------------------------------------------------
start_conn(State) ->
{ok, Socket} = gen_tcp:connect(State#state.host, State#state.port,
[binary,
{packet, 0}]),
Key = base64:encode_to_string(crypto:strong_rand_bytes(16)),
gen_tcp:send(Socket,
"GET " ++ State#state.path ++ " HTTP/1.1\r\n" ++
"Host: " ++ State#state.addr ++ "\r\n" ++
"Upgrade: websocket\r\n" ++
"Connection: Upgrade\r\n" ++
"Sec-WebSocket-Key: " ++ Key ++ "\r\n" ++
"Origin: null\r\n" ++
"Sec-WebSocket-Protocol: mqtt\r\n" ++
"Sec-WebSocket-Version: 13\r\n\r\n"),
loop(State#state{socket = Socket,
data = <<>>,
phase = opening}).
do_recv(State = #state{phase = opening, ppid = PPid, data = Data}) ->
case split("\r\n\r\n", binary_to_list(Data), 1, empty) of
[_Http, empty] -> State;
[Http, Data1] ->
%% TODO: don't ignore http response data, verify key
PPid ! {rfc6455, open, self(), [{http_response, Http}]},
State#state{phase = open,
data = Data1}
end;
do_recv(State = #state{phase = Phase, data = Data, socket = Socket, ppid = PPid})
when Phase =:= open orelse Phase =:= closing ->
R = case Data of
<<F:1, _:3, O:4, 0:1, L:7, Payload:L/binary, Rest/binary>>
when L < 126 ->
{F, O, Payload, Rest};
<<F:1, _:3, O:4, 0:1, 126:7, L2:16, Payload:L2/binary, Rest/binary>> ->
{F, O, Payload, Rest};
<<F:1, _:3, O:4, 0:1, 127:7, L2:64, Payload:L2/binary, Rest/binary>> ->
{F, O, Payload, Rest};
<<_:1, _:3, _:4, 1:1, _/binary>> ->
%% According o rfc6455 5.1 the server must not mask any frames.
die(Socket, PPid, {1006, "Protocol error"}, normal);
_ ->
moredata
end,
case R of
moredata ->
State;
_ -> do_recv2(State, R)
end.
do_recv2(State = #state{phase = Phase, socket = Socket, ppid = PPid}, R) ->
case R of
{1, 1, Payload, Rest} ->
PPid ! {rfc6455, recv, self(), Payload},
State#state{data = Rest};
{1, 2, Payload, Rest} ->
PPid ! {rfc6455, recv_binary, self(), Payload},
State#state{data = Rest};
{1, 8, Payload, _Rest} ->
WsReason = case Payload of
<<WC:16, WR/binary>> -> {WC, WR};
<<>> -> {1005, "No status received"}
end,
case Phase of
open -> %% echo
do_close(State, WsReason),
gen_tcp:close(Socket);
closing ->
ok
end,
die(Socket, PPid, WsReason, normal);
{_, _, _, _Rest2} ->
io:format("Unknown frame type~n"),
die(Socket, PPid, {1006, "Unknown frame type"}, normal)
end.
encode_frame(F, O, Payload) ->
Mask = crypto:strong_rand_bytes(4),
MaskedPayload = apply_mask(Mask, iolist_to_binary(Payload)),
L = byte_size(MaskedPayload),
IoData = case L of
_ when L < 126 ->
[<<F:1, 0:3, O:4, 1:1, L:7>>, Mask, MaskedPayload];
_ when L < 65536 ->
[<<F:1, 0:3, O:4, 1:1, 126:7, L:16>>, Mask, MaskedPayload];
_ ->
[<<F:1, 0:3, O:4, 1:1, 127:7, L:64>>, Mask, MaskedPayload]
end,
iolist_to_binary(IoData).
do_send(State = #state{socket = Socket}, Payload) ->
gen_tcp:send(Socket, encode_frame(1, 1, Payload)),
State.
do_send_binary(State = #state{socket = Socket}, Payload) ->
gen_tcp:send(Socket, encode_frame(1, 2, Payload)),
State.
do_close(State = #state{socket = Socket}, {Code, Reason}) ->
Payload = iolist_to_binary([<<Code:16>>, Reason]),
gen_tcp:send(Socket, encode_frame(1, 8, Payload)),
State#state{phase = closing}.
loop(State = #state{socket = Socket, ppid = PPid, data = Data,
phase = Phase}) ->
receive
{tcp, Socket, Bin} ->
State1 = State#state{data = iolist_to_binary([Data, Bin])},
loop(do_recv(State1));
{send, Payload} when Phase == open ->
loop(do_send(State, Payload));
{send_binary, Payload} when Phase == open ->
loop(do_send_binary(State, Payload));
{tcp_closed, Socket} ->
die(Socket, PPid, {1006, "Connection closed abnormally"}, normal);
{close, WsReason} when Phase == open ->
loop(do_close(State, WsReason))
end.
die(Socket, PPid, WsReason, Reason) ->
gen_tcp:shutdown(Socket, read_write),
PPid ! {rfc6455, close, self(), WsReason},
exit(Reason).
%% --------------------------------------------------------------------------
split(SubStr, Str, Limit) ->
split(SubStr, Str, Limit, "").
split(SubStr, Str, Limit, Default) ->
Acc = split(SubStr, Str, Limit, [], Default),
lists:reverse(Acc).
split(_SubStr, Str, 0, Acc, _Default) -> [Str | Acc];
split(SubStr, Str, Limit, Acc, Default) ->
{L, R} = case string:str(Str, SubStr) of
0 -> {Str, Default};
I -> {string:substr(Str, 1, I-1),
string:substr(Str, I+length(SubStr))}
end,
split(SubStr, R, Limit-1, [L | Acc], Default).
apply_mask(Mask, Data) when is_number(Mask) ->
apply_mask(<<Mask:32>>, Data);
apply_mask(<<0:32>>, Data) ->
Data;
apply_mask(Mask, Data) ->
iolist_to_binary(lists:reverse(apply_mask2(Mask, Data, []))).
apply_mask2(M = <<Mask:32>>, <<Data:32, Rest/binary>>, Acc) ->
T = Data bxor Mask,
apply_mask2(M, Rest, [<<T:32>> | Acc]);
apply_mask2(<<Mask:24, _:8>>, <<Data:24>>, Acc) ->
T = Data bxor Mask,
[<<T:24>> | Acc];
apply_mask2(<<Mask:16, _:16>>, <<Data:16>>, Acc) ->
T = Data bxor Mask,
[<<T:16>> | Acc];
apply_mask2(<<Mask:8, _:24>>, <<Data:8>>, Acc) ->
T = Data bxor Mask,
[<<T:8>> | Acc];
apply_mask2(_, <<>>, Acc) ->
Acc.

View File

@ -1,75 +0,0 @@
-module(ws_client).
-export([
start_link/0,
start_link/1,
send_binary/2,
send_ping/2,
recv/2,
recv/1,
stop/1
]).
-export([
init/2,
websocket_handle/3,
websocket_info/3,
websocket_terminate/3
]).
-record(state, {
buffer = [] :: list(),
waiting = undefined :: undefined | pid()
}).
start_link() ->
start_link("ws://localhost:8083/mqtt").
start_link(Url) ->
websocket_client:start_link(Url, ?MODULE, [], [{extra_headers, [{"Sec-Websocket-Protocol", "mqtt"}]}]).
stop(Pid) ->
Pid ! stop.
send_binary(Pid, Msg) ->
websocket_client:cast(Pid, {binary, Msg}).
send_ping(Pid, Msg) ->
websocket_client:cast(Pid, {ping, Msg}).
recv(Pid) ->
recv(Pid, 5000).
recv(Pid, Timeout) ->
Pid ! {recv, self()},
receive
M -> M
after
Timeout -> error
end.
init(_, _WSReq) ->
{ok, #state{}}.
websocket_handle(Frame, _, State = #state{waiting = undefined, buffer = Buffer}) ->
logger:info("Client received frame~p", [Frame]),
{ok, State#state{buffer = [Frame|Buffer]}};
websocket_handle(Frame, _, State = #state{waiting = From}) ->
logger:info("Client received frame~p", [Frame]),
From ! Frame,
{ok, State#state{waiting = undefined}}.
websocket_info({send_text, Text}, WSReq, State) ->
websocket_client:send({text, Text}, WSReq),
{ok, State};
websocket_info({recv, From}, _, State = #state{buffer = []}) ->
{ok, State#state{waiting = From}};
websocket_info({recv, From}, _, State = #state{buffer = [Top|Rest]}) ->
From ! Top,
{ok, State#state{buffer = Rest}};
websocket_info(stop, _, State) ->
{close, <<>>, State}.
websocket_terminate(Close, _, State) ->
io:format("Websocket closed with frame ~p and state ~p", [Close, State]),
ok.