From a09b87fc945dca474d09aa214165a75cead577aa Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Tue, 15 Oct 2019 17:28:20 +0800 Subject: [PATCH 01/40] Add more test cases for connecton module using meck --- test/emqx_connection_SUITE.erl | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index 39cc67dee..309d2a6b5 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -21,17 +21,28 @@ -include_lib("eunit/include/eunit.hrl"). +-define(Transport, esockd_transport). + all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> - emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([]), + ok = meck:new(esockd_transport, [passthrough, no_history]), + ok = meck:new(emqx_channel, [passthrough, no_history]), Config. end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). + ok. -t_basic(_) -> +t_start_link_error(_) -> + process_flag(trap_exit, true), + ok = meck:expect(esockd_transport, wait, fun(_Sock) -> {error, enotconn} end), + ok = meck:expect(esockd_transport, fast_close, fun(_Sock) -> ok end), + {ok, Pid} = emqx_connection:start_link(esockd_transport, socket, []), + timer:sleep(100), + ?assertNot(erlang:is_process_alive(Pid)), + ?assertEqual([{'EXIT', Pid, normal}], proc_mailbox()). + +todo_t_basic(_) -> Topic = <<"TopicA">>, {ok, C} = emqtt:start_link([{port, 1883}, {clientid, <<"hello">>}]), {ok, _} = emqtt:connect(C), @@ -43,6 +54,12 @@ t_basic(_) -> ?assertEqual(3, length(recv_msgs(3))), ok = emqtt:disconnect(C). +proc_mailbox() -> + proc_mailbox(self()). +proc_mailbox(Pid) -> + {messages, Msgs} = erlang:process_info(Pid, messages), + Msgs. + recv_msgs(Count) -> recv_msgs(Count, []). From a0e72fd0402b2cb07447ee9c14c5722e282bef53 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 18 Oct 2019 18:52:57 +0800 Subject: [PATCH 02/40] Breaking change of listener's rate_limit config --- etc/emqx.conf | 51 +++++++++++++++++------------------------------- priv/emqx.schema | 34 ++++++++++++++++++++------------ 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/etc/emqx.conf b/etc/emqx.conf index 9415834ef..c2d2ad75f 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -562,8 +562,8 @@ zone.external.hibernate_after = 60s ## Publish limit for the external MQTT connections. ## ## Value: Number,Duration -## Example: 10 messages per minute. -## zone.external.publish_limit = 10,1m +## Example: 100 messages per 10 seconds. +## zone.external.publish_limit = 100,10s ## Enable ACL check. ## @@ -874,14 +874,11 @@ listener.tcp.external.active_n = 100 ## Value: String listener.tcp.external.zone = external -## Rate limit for the external MQTT/TCP connections. Format is 'rate,burst'. +## Rate limit for the external MQTT/TCP connections. Format is 'limit,duration'. ## -## Value: rate,burst -## - rate: The average limit value for per second -## - burst: The maximum allowed for each check, To avoid frequent restriction -## this value is recommended to be set to `(max_packet_size * active_n)/2` -## Unit: Bps -## listener.tcp.external.rate_limit = 1024,52428800 +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.tcp.external.rate_limit = 100KB,10s ## The access control rules for the MQTT/TCP listener. ## @@ -1010,12 +1007,9 @@ listener.tcp.internal.zone = internal ## ## See: listener.tcp.$name.rate_limit ## -## Value: rate,burst -## - rate: The average limit value for per second -## - burst: The maximum allowed for each check, To avoid frequent restriction -## this value is recommended to be set to `(max_packet_size * active_n)/2` -## Unit: Bps -## listener.tcp.internal.rate_limit = 1000000,524288000 +## Value: limit,duration +## Default: 1MB incoming per second. +## listener.tcp.internal.rate_limit = 1MB,1s ## The TCP backlog of internal MQTT/TCP Listener. ## @@ -1123,12 +1117,9 @@ listener.ssl.external.access.1 = allow all ## Rate limit for the external MQTT/SSL connections. ## -## Value: rate,burst -## - rate: The average limit value for per second -## - burst: The maximum allowed for each check, To avoid frequent restriction -## this value is recommended to be set to `(max_packet_size * active_n)/2` -## Unit: Bps -## listener.ssl.external.rate_limit = 1024,52428800 +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.ssl.external.rate_limit = 100KB,10s ## Enable the Proxy Protocol V1/2 if the EMQ cluster is deployed behind ## HAProxy or Nginx. @@ -1360,12 +1351,9 @@ listener.ws.external.max_conn_rate = 1000 ## Rate limit for the MQTT/WebSocket connections. ## -## Value: rate,burst -## - rate: The average limit value for per second -## - burst: The maximum allowed for each check, To avoid frequent restriction -## this value is recommended to be set to `(max_packet_size * 1)/2` -## Unit: Bps -## listener.ws.external.rate_limit = 1024,524288 +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.ws.external.rate_limit = 100KB,10s ## Zone of the external MQTT/WebSocket listener belonged to. ## @@ -1571,12 +1559,9 @@ listener.wss.external.max_conn_rate = 1000 ## Rate limit for the MQTT/WebSocket/SSL connections. ## -## Value: rate,burst -## - rate: The average limit value for per second -## - burst: The maximum allowed for each check, To avoid frequent restriction -## this value is recommended to be set to `(max_packet_size * 1)/2` -## Unit: Bps -## listener.wss.external.rate_limit = 1024,524288 +## Value: limit,duration +## Default: 100KB incoming per 10 seconds. +## listener.wss.external.rate_limit = 100KB,10s ## Zone of the external MQTT/WebSocket/SSL listener belonged to. ## diff --git a/priv/emqx.schema b/priv/emqx.schema index abe646a10..d751ecd06 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -939,14 +939,14 @@ end}. ("shared_subscription", Val) -> {shared_subscription, Val}; ("publish_limit", Val) -> - [Limit, Duration] = string:tokens(Val, ", "), - PubLimit = case cuttlefish_duration:parse(Duration, s) of - Secs when is_integer(Secs) -> - {list_to_integer(Limit) / Secs, list_to_integer(Limit)}; - {error, Reason} -> - error(Reason) + [L, D] = string:tokens(Val, ", "), + Limit = list_to_integer(L), + Duration = case cuttlefish_duration:parse(D, s) of + Secs when is_integer(Secs) -> Secs; + {error, Reason} -> error(Reason) end, - {publish_limit, PubLimit}; + Rate = Limit / Duration, + {publish_limit, {Rate, Limit}}; ("force_gc_policy", Val) -> [Count, Bytes] = string:tokens(Val, "| "), GcPolicy = case cuttlefish_bytesize:parse(Bytes) of @@ -1644,10 +1644,20 @@ end}. end end, - Ratelimit = fun(undefined) -> - undefined; - (S) -> - list_to_tuple([list_to_integer(Token) || Token <- string:tokens(S, ",")]) + RateLimit = fun(undefined) -> + undefined; + (Val) -> + [L, D] = string:tokens(Val, ", "), + Limit = case cuttlefish_bytesize:parse(L) of + Sz when is_integer(Sz) -> Sz; + {error, Reason} -> error(Reason) + end, + Duration = case cuttlefish_duration:parse(D, s) of + Secs when is_integer(Secs) -> Secs; + {error, Reason1} -> error(Reason1) + end, + Rate = Limit / Duration, + {Rate, Limit} end, LisOpts = fun(Prefix) -> @@ -1658,7 +1668,7 @@ end}. {active_n, cuttlefish:conf_get(Prefix ++ ".active_n", Conf, undefined)}, {tune_buffer, cuttlefish:conf_get(Prefix ++ ".tune_buffer", Conf, undefined)}, {zone, Atom(cuttlefish:conf_get(Prefix ++ ".zone", Conf, undefined))}, - {rate_limit, Ratelimit(cuttlefish:conf_get(Prefix ++ ".rate_limit", Conf, undefined))}, + {rate_limit, RateLimit(cuttlefish:conf_get(Prefix ++ ".rate_limit", Conf, undefined))}, {proxy_protocol, cuttlefish:conf_get(Prefix ++ ".proxy_protocol", Conf, undefined)}, {proxy_protocol_timeout, cuttlefish:conf_get(Prefix ++ ".proxy_protocol_timeout", Conf, undefined)}, {verify_protocol_header, cuttlefish:conf_get(Prefix ++ ".verify_protocol_header", Conf, undefined)}, From cd4adbada033a0fdfee04728ffa1c47293da940f Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 18 Oct 2019 18:53:31 +0800 Subject: [PATCH 03/40] Add more test cases for connection, channel and session modules --- src/emqx_broker.erl | 1 + src/emqx_channel.erl | 236 +++++++++-------- src/emqx_connection.erl | 127 ++++----- src/emqx_session.erl | 11 +- src/emqx_ws_connection.erl | 35 +-- test/emqx_channel_SUITE.erl | 421 ++++++++++++++++++++---------- test/emqx_connection_SUITE.erl | 325 ++++++++++++++++++++--- test/emqx_session_SUITE.erl | 327 ++++------------------- test/emqx_ws_connection_SUITE.erl | 214 +++++++++++++-- 9 files changed, 1037 insertions(+), 660 deletions(-) diff --git a/src/emqx_broker.erl b/src/emqx_broker.erl index f171d3e98..8b6693173 100644 --- a/src/emqx_broker.erl +++ b/src/emqx_broker.erl @@ -486,3 +486,4 @@ code_change(_OldVsn, State, _Extra) -> %%------------------------------------------------------------------------------ %% Internal functions %%------------------------------------------------------------------------------ + diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 5f4821897..012c35852 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -24,6 +24,11 @@ -logger_header("[Channel]"). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + -export([ info/1 , info/2 , attrs/1 @@ -31,9 +36,6 @@ , caps/1 ]). -%% Test Exports --export([set_field/3]). - -export([ init/2 , handle_in/2 , handle_out/2 @@ -43,6 +45,13 @@ , terminate/2 ]). +-export([ recvd/2 + , sent/2 + ]). + +%% export for ct +-export([set_field/3]). + -import(emqx_misc, [ run_fold/3 , pipeline/3 @@ -219,17 +228,17 @@ init_gc_state(Zone) -> %% Handle incoming packet %%-------------------------------------------------------------------- --spec(handle_in(Bytes :: pos_integer() | emqx_types:packet(), channel()) +-spec(recvd(pos_integer(), channel()) -> channel()). +recvd(Bytes, Channel) -> + ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)). + +-spec(handle_in(emqx_types:packet(), channel()) -> {ok, channel()} | {ok, output(), channel()} - | {stop, Reason :: term(), channel()} - | {stop, Reason :: term(), output(), channel()}). -handle_in(Bytes, Channel) when is_integer(Bytes) -> - NChannel = maybe_gc_and_check_oom(Bytes, Channel), - {ok, ensure_timer(stats_timer, NChannel)}; - + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), output(), channel()}). handle_in(?CONNECT_PACKET(_), Channel = #channel{conn_state = connected}) -> - handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel); + handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel); handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> case pipeline([fun enrich_conninfo/2, @@ -243,7 +252,7 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> process_connect(NConnPkt, NChannel); {error, ReasonCode, NChannel} -> ReasonName = emqx_reason_codes:formalized(connack, ReasonCode), - handle_out({connack, ReasonName, ConnPkt}, NChannel) + handle_out(connack, {ReasonName, ConnPkt}, NChannel) end; handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> @@ -251,7 +260,7 @@ handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> case emqx_packet:check(Packet) of ok -> handle_publish(Packet, NChannel); {error, ReasonCode} -> - handle_out({disconnect, ReasonCode}, NChannel) + handle_out(disconnect, ReasonCode, NChannel) end; handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), @@ -281,26 +290,27 @@ handle_in(?PUBREC_PACKET(PacketId, _ReasonCode), {ok, Msg, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), NChannel = Channel1#channel{session = NSession}, - handle_out({pubrel, PacketId, ?RC_SUCCESS}, NChannel); + handle_out(pubrel, {PacketId, ?RC_SUCCESS}, NChannel); {error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} -> ?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]), ok = emqx_metrics:inc('packets.pubrec.inuse'), - handle_out({pubrel, PacketId, RC}, Channel1); + handle_out(pubrel, {PacketId, RC}, Channel1); {error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBREC ~w is not found.", [PacketId]), ok = emqx_metrics:inc('packets.pubrec.missed'), - handle_out({pubrel, PacketId, RC}, Channel1) + handle_out(pubrel, {PacketId, RC}, Channel1) end; handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> Channel1 = inc_pub_stats(pubrel_in, Channel), case emqx_session:pubrel(PacketId, Session) of {ok, NSession} -> - handle_out({pubcomp, PacketId, ?RC_SUCCESS}, Channel1#channel{session = NSession}); + Channel2 = Channel1#channel{session = NSession}, + handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, Channel2); {error, NotFound} -> ok = emqx_metrics:inc('packets.pubrel.missed'), ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]), - handle_out({pubcomp, PacketId, NotFound}, Channel1) + handle_out(pubcomp, {PacketId, NotFound}, Channel1) end; handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> @@ -324,9 +334,9 @@ handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), parse_topic_filters(TopicFilters)), TopicFilters2 = enrich_subid(Properties, TopicFilters1), {ReasonCodes, NChannel} = process_subscribe(TopicFilters2, Channel), - handle_out({suback, PacketId, ReasonCodes}, NChannel); + handle_out(suback, {PacketId, ReasonCodes}, NChannel); {error, ReasonCode} -> - handle_out({disconnect, ReasonCode}, Channel) + handle_out(disconnect, ReasonCode, Channel) end; handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), @@ -336,9 +346,9 @@ handle_in(Packet = ?UNSUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), [ClientInfo, Properties], parse_topic_filters(TopicFilters)), {ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel), - handle_out({unsuback, PacketId, ReasonCodes}, NChannel); + handle_out(unsuback, {PacketId, ReasonCodes}, NChannel); {error, ReasonCode} -> - handle_out({disconnect, ReasonCode}, Channel) + handle_out(disconnect, ReasonCode, Channel) end; handle_in(?PACKET(?PINGREQ), Channel) -> @@ -355,7 +365,7 @@ handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninf Interval = emqx_mqtt_props:get('Session-Expiry-Interval', Properties, OldInterval), if OldInterval == 0 andalso Interval > OldInterval -> - handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel1); + handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel1); Interval == 0 -> shutdown(ReasonName, Channel1); true -> @@ -364,7 +374,7 @@ handle_in(?DISCONNECT_PACKET(ReasonCode, Properties), Channel = #channel{conninf end; handle_in(?AUTH_PACKET(), Channel) -> - handle_out({disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR}, Channel); + handle_out(disconnect, ?RC_IMPLEMENTATION_SPECIFIC_ERROR, Channel); handle_in({frame_error, Reason}, Channel = #channel{conn_state = idle}) -> shutdown(Reason, Channel); @@ -373,7 +383,7 @@ handle_in({frame_error, Reason}, Channel = #channel{conn_state = connecting}) -> shutdown(Reason, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), Channel); handle_in({frame_error, _Reason}, Channel = #channel{conn_state = connected}) -> - handle_out({disconnect, ?RC_MALFORMED_PACKET}, Channel); + handle_out(disconnect, ?RC_MALFORMED_PACKET, Channel); handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) -> ?LOG(error, "Unexpected frame error: ~p", [Reason]), @@ -381,7 +391,7 @@ handle_in({frame_error, Reason}, Channel = #channel{conn_state = disconnected}) handle_in(Packet, Channel) -> ?LOG(error, "Unexpected incoming: ~p", [Packet]), - handle_out({disconnect, ?RC_PROTOCOL_ERROR}, Channel). + handle_out(disconnect, ?RC_PROTOCOL_ERROR, Channel). %%-------------------------------------------------------------------- %% Process Connect @@ -392,18 +402,18 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, case emqx_cm:open_session(CleanStart, ClientInfo, ConnInfo) of {ok, #{session := Session, present := false}} -> NChannel = Channel#channel{session = Session}, - handle_out({connack, ?RC_SUCCESS, sp(false), ConnPkt}, NChannel); + handle_out(connack, {?RC_SUCCESS, sp(false), ConnPkt}, NChannel); {ok, #{session := Session, present := true, pendings := Pendings}} -> %%TODO: improve later. NPendings = lists:usort(lists:append(Pendings, emqx_misc:drain_deliver())), NChannel = Channel#channel{session = Session, resuming = true, pendings = NPendings}, - handle_out({connack, ?RC_SUCCESS, sp(true), ConnPkt}, NChannel); + handle_out(connack, {?RC_SUCCESS, sp(true), ConnPkt}, NChannel); {error, Reason} -> %% TODO: Unknown error? ?LOG(error, "Failed to open session: ~p", [Reason]), - handle_out({connack, ?RC_UNSPECIFIED_ERROR, ConnPkt}, Channel) + handle_out(connack, {?RC_UNSPECIFIED_ERROR, ConnPkt}, Channel) end. %%-------------------------------------------------------------------- @@ -426,7 +436,7 @@ handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId), {error, ReasonCode, NChannel} -> ?LOG(warning, "Cannot publish message to ~s due to ~s", [Topic, emqx_reason_codes:text(ReasonCode, ProtoVer)]), - handle_out({disconnect, ReasonCode}, NChannel) + handle_out(disconnect, ReasonCode, NChannel) end. process_publish(Packet = ?PUBLISH_PACKET(_QoS, _Topic, PacketId), Channel) -> @@ -442,7 +452,7 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_1}, Channel) -> [] -> ?RC_NO_MATCHING_SUBSCRIBERS; _ -> ?RC_SUCCESS end, - handle_out({puback, PacketId, ReasonCode}, Channel); + handle_out(puback, {PacketId, ReasonCode}, Channel); process_publish(PacketId, Msg = #message{qos = ?QOS_2}, Channel = #channel{session = Session}) -> @@ -453,14 +463,14 @@ process_publish(PacketId, Msg = #message{qos = ?QOS_2}, _ -> ?RC_SUCCESS end, NChannel = Channel#channel{session = NSession}, - handle_out({pubrec, PacketId, RC}, ensure_timer(await_timer, NChannel)); + handle_out(pubrec, {PacketId, RC}, ensure_timer(await_timer, NChannel)); {error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} -> ok = emqx_metrics:inc('packets.publish.inuse'), - handle_out({pubrec, PacketId, RC}, Channel); + handle_out(pubrec, {PacketId, RC}, Channel); {error, RC = ?RC_RECEIVE_MAXIMUM_EXCEEDED} -> ?LOG(warning, "Dropped qos2 packet ~w due to awaiting_rel is full", [PacketId]), ok = emqx_metrics:inc('messages.qos2.dropped'), - handle_out({pubrec, PacketId, RC}, Channel) + handle_out(pubrec, {PacketId, RC}, Channel) end. publish_to_msg(Packet, #channel{conninfo = #{proto_ver := ProtoVer}, @@ -528,15 +538,15 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel = %% Handle outgoing packet %%-------------------------------------------------------------------- --spec(handle_out(integer()|term(), channel()) +-spec(sent(pos_integer(), channel()) -> channel()). +sent(Bytes, Channel) -> + ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)). + +-spec(handle_out(term(), channel()) -> {ok, channel()} | {ok, output(), channel()} - | {stop, Reason :: term(), channel()} - | {stop, Reason :: term(), output(), channel()}). -handle_out(Bytes, Channel) when is_integer(Bytes) -> - NChannel = maybe_gc_and_check_oom(Bytes, Channel), - {ok, ensure_timer(stats_timer, NChannel)}; - + | {shutdown, Reason :: term(), channel()} + | {shutdown, Reason :: term(), output(), channel()}). handle_out(Delivers, Channel = #channel{conn_state = disconnected, session = Session}) when is_list(Delivers) -> @@ -557,42 +567,6 @@ handle_out(Delivers, Channel = #channel{session = Session}) when is_list(Deliver {ok, Channel#channel{session = NSession}} end; -handle_out({connack, ?RC_SUCCESS, SP, ConnPkt}, - Channel = #channel{conninfo = ConnInfo, - clientinfo = ClientInfo}) -> - AckProps = run_fold([fun enrich_caps/2, - fun enrich_server_keepalive/2, - fun enrich_assigned_clientid/2], #{}, Channel), - ConnInfo1 = ConnInfo#{connected_at => erlang:system_time(second)}, - Channel1 = Channel#channel{conninfo = ConnInfo1, - will_msg = emqx_packet:will_msg(ConnPkt), - conn_state = connected, - alias_maximum = init_alias_maximum(ConnPkt, ClientInfo) - }, - Channel2 = ensure_keepalive(AckProps, Channel1), - ok = emqx_hooks:run('client.connected', [ClientInfo, ?RC_SUCCESS, ConnInfo]), - AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps), - case maybe_resume_session(Channel2) of - ignore -> - {ok, [{enter, connected}, {outgoing, AckPacket}], Channel2}; - {ok, Publishes, NSession} -> - Channel3 = Channel2#channel{session = NSession, - resuming = false, - pendings = []}, - {ok, {outgoing, Packets}, _} = handle_out({publish, Publishes}, Channel3), - {ok, [{enter, connected}, {outgoing, [AckPacket|Packets]}], Channel3} - end; - -handle_out({connack, ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, - clientinfo = ClientInfo}) -> - ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]), - ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of - ?MQTT_PROTO_V5 -> ReasonCode; - _Ver -> emqx_reason_codes:compat(connack, ReasonCode) - end, - Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer), - shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel); - handle_out({publish, Publishes}, Channel) when is_list(Publishes) -> Packets = lists:foldl( fun(Publish, Acc) -> @@ -618,40 +592,80 @@ handle_out({publish, PacketId, Msg}, Channel = Msg3 = emqx_mountpoint:unmount(MountPoint, Msg2), {ok, emqx_message:to_packet(PacketId, Msg3), Channel}; -handle_out({puback, PacketId, ReasonCode}, Channel) -> +handle_out(Data, Channel) -> + ?LOG(error, "Unexpected outgoing: ~p", [Data]), + {ok, Channel}. + +handle_out(connack, {?RC_SUCCESS, SP, ConnPkt}, + Channel = #channel{conninfo = ConnInfo, + clientinfo = ClientInfo}) -> + AckProps = run_fold([fun enrich_caps/2, + fun enrich_server_keepalive/2, + fun enrich_assigned_clientid/2], #{}, Channel), + ConnInfo1 = ConnInfo#{connected_at => erlang:system_time(second)}, + Channel1 = Channel#channel{conninfo = ConnInfo1, + will_msg = emqx_packet:will_msg(ConnPkt), + conn_state = connected, + alias_maximum = init_alias_maximum(ConnPkt, ClientInfo) + }, + Channel2 = ensure_keepalive(AckProps, Channel1), + ok = emqx_hooks:run('client.connected', [ClientInfo, ?RC_SUCCESS, ConnInfo]), + AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps), + case maybe_resume_session(Channel2) of + ignore -> + {ok, [{enter, connected}, {outgoing, AckPacket}], Channel2}; + {ok, Publishes, NSession} -> + Channel3 = Channel2#channel{session = NSession, + resuming = false, + pendings = []}, + {ok, {outgoing, Packets}, _} = handle_out({publish, Publishes}, Channel3), + {ok, [{enter, connected}, {outgoing, [AckPacket|Packets]}], Channel3} + end; + +handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, + clientinfo = ClientInfo}) -> + ok = emqx_hooks:run('client.connected', [ClientInfo, ReasonCode, ConnInfo]), + ReasonCode1 = case ProtoVer = maps:get(proto_ver, ConnInfo) of + ?MQTT_PROTO_V5 -> ReasonCode; + _Ver -> emqx_reason_codes:compat(connack, ReasonCode) + end, + Reason = emqx_reason_codes:name(ReasonCode1, ProtoVer), + shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel); + +handle_out(puback, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)}; -handle_out({pubrel, PacketId, ReasonCode}, Channel) -> +handle_out(pubrel, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)}; -handle_out({pubrec, PacketId, ReasonCode}, Channel) -> +handle_out(pubrec, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)}; -handle_out({pubcomp, PacketId, ReasonCode}, Channel) -> +handle_out(pubcomp, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)}; -handle_out({suback, PacketId, ReasonCodes}, +handle_out(suback, {PacketId, ReasonCodes}, Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> {ok, ?SUBACK_PACKET(PacketId, ReasonCodes), Channel}; -handle_out({suback, PacketId, ReasonCodes}, Channel) -> +handle_out(suback, {PacketId, ReasonCodes}, Channel) -> ReasonCodes1 = [emqx_reason_codes:compat(suback, RC) || RC <- ReasonCodes], {ok, ?SUBACK_PACKET(PacketId, ReasonCodes1), Channel}; -handle_out({unsuback, PacketId, ReasonCodes}, +handle_out(unsuback, {PacketId, ReasonCodes}, Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> {ok, ?UNSUBACK_PACKET(PacketId, ReasonCodes), Channel}; -handle_out({unsuback, PacketId, _ReasonCodes}, Channel) -> +handle_out(unsuback, {PacketId, _ReasonCodes}, Channel) -> {ok, ?UNSUBACK_PACKET(PacketId), Channel}; -handle_out({disconnect, ReasonCode}, Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) -> +handle_out(disconnect, ReasonCode, Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) + when is_integer(ReasonCode) -> ReasonName = emqx_reason_codes:name(ReasonCode, ProtoVer), - handle_out({disconnect, ReasonCode, ReasonName}, Channel); + handle_out(disconnect, {ReasonCode, ReasonName}, Channel); -handle_out({disconnect, ReasonCode, ReasonName}, - Channel = #channel{conninfo = #{proto_ver := ProtoVer, - expiry_interval := ExpiryInterval}}) -> +handle_out(disconnect, {ReasonCode, ReasonName}, Channel = #channel{conninfo = ConnInfo}) -> + #{proto_ver := ProtoVer, expiry_interval := ExpiryInterval} = ConnInfo, case {ExpiryInterval, ProtoVer} of {0, ?MQTT_PROTO_V5} -> shutdown(ReasonName, ?DISCONNECT_PACKET(ReasonCode), Channel); @@ -673,7 +687,7 @@ handle_out({disconnect, ReasonCode, ReasonName}, {ok, {close, ReasonName}, NChannel} end; -handle_out({Type, Data}, Channel) -> +handle_out(Type, Data, Channel) -> ?LOG(error, "Unexpected outgoing: ~s, ~p", [Type, Data]), {ok, Channel}. @@ -683,38 +697,40 @@ handle_out({Type, Data}, Channel) -> -spec(handle_call(Req :: term(), channel()) -> {reply, Reply :: term(), channel()} - | {stop, Reason :: term(), Reply :: term(), channel()}). + | {shutdown, Reason :: term(), Reply :: term(), channel()}). handle_call(kick, Channel) -> - {stop, {shutdown, kicked}, ok, Channel}; + shutdown(kicked, ok, Channel); handle_call(discard, Channel = #channel{conn_state = connected}) -> Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER), - {stop, {shutdown, discarded}, ok, Packet, Channel}; + {shutdown, discarded, ok, Packet, Channel}; handle_call(discard, Channel = #channel{conn_state = disconnected}) -> - {stop, {shutdown, discarded}, ok, Channel}; + shutdown(discarded, ok, Channel); %% Session Takeover handle_call({takeover, 'begin'}, Channel = #channel{session = Session}) -> - {reply, Session, Channel#channel{takeover = true}}; + reply(Session, Channel#channel{takeover = true}); handle_call({takeover, 'end'}, Channel = #channel{session = Session, pendings = Pendings}) -> ok = emqx_session:takeover(Session), + %% TODO: Should not drain deliver here Delivers = emqx_misc:drain_deliver(), AllPendings = lists:append(Delivers, Pendings), - {stop, {shutdown, takeovered}, AllPendings, Channel}; + shutdown(takeovered, AllPendings, Channel); handle_call(Req, Channel) -> ?LOG(error, "Unexpected call: ~p", [Req]), - {reply, ignored, Channel}. + reply(ignored, Channel). %%-------------------------------------------------------------------- %% Handle Info %%-------------------------------------------------------------------- -spec(handle_info(Info :: term(), channel()) - -> ok | {ok, channel()} | {stop, Reason :: term(), channel()}). + -> ok | {ok, channel()} + | {shutdown, Reason :: term(), channel()}). handle_info({subscribe, TopicFilters}, Channel = #channel{clientinfo = ClientInfo}) -> TopicFilters1 = emqx_hooks:run_fold('client.subscribe', [ClientInfo, #{'Internal' => true}], @@ -769,7 +785,7 @@ handle_info(Info, Channel) -> -spec(handle_timeout(reference(), Msg :: term(), channel()) -> {ok, channel()} | {ok, Result :: term(), channel()} - | {stop, Reason :: term(), channel()}). + | {shutdown, Reason :: term(), channel()}). handle_timeout(TRef, {emit_stats, Stats}, Channel = #channel{clientinfo = #{clientid := ClientId}, timers = #{stats_timer := TRef}}) -> @@ -784,7 +800,7 @@ handle_timeout(TRef, {keepalive, StatVal}, NChannel = Channel#channel{keepalive = NKeepalive}, {ok, reset_timer(alive_timer, NChannel)}; {error, timeout} -> - handle_out({disconnect, ?RC_KEEP_ALIVE_TIMEOUT}, Channel) + handle_out(disconnect, ?RC_KEEP_ALIVE_TIMEOUT, Channel) end; handle_timeout(TRef, retry_delivery, @@ -1195,15 +1211,21 @@ maybe_gc_and_check_oom(Oct, Channel = #channel{clientinfo = #{zone := Zone}, %% Helper functions %%-------------------------------------------------------------------- +-compile({inline, [reply/2]}). +reply(Reply, Channel) -> + {reply, Reply, Channel}. + +-compile({inline, [shutdown/2]}). +shutdown(Reason, Channel) -> + {shutdown, Reason, Channel}. + +-compile({inline, [shutdown/3]}). +shutdown(Reason, Reply, Channel) -> + {shutdown, Reason, Reply, Channel}. + sp(true) -> 1; sp(false) -> 0. flag(true) -> 1; flag(false) -> 0. -shutdown(Reason, Channel) -> - {stop, {shutdown, Reason}, Channel}. - -shutdown(Reason, Packets, Channel) -> - {stop, {shutdown, Reason}, Packets, Channel}. - diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 02d521814..1da494dfa 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -24,6 +24,11 @@ -logger_header("[MQTT]"). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + %% API -export([ start_link/3 , stop/1 @@ -121,9 +126,7 @@ info(active_n, #state{active_n = ActiveN}) -> info(pub_limit, #state{pub_limit = PubLimit}) -> limit_info(PubLimit); info(rate_limit, #state{rate_limit = RateLimit}) -> - limit_info(RateLimit); -info(channel, #state{channel = Channel}) -> - emqx_channel:info(Channel). + limit_info(RateLimit). limit_info(Limit) -> emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit). @@ -158,17 +161,9 @@ init(Parent, Transport, RawSocket, Options) -> case Transport:wait(RawSocket) of {ok, Socket} -> do_init(Parent, Transport, Socket, Options); - {error, Reason} when Reason =:= enotconn; - Reason =:= einval; - Reason =:= closed -> - Transport:fast_close(RawSocket), - exit(normal); - {error, timeout} -> - Transport:fast_close(RawSocket), - exit({shutdown, ssl_upgrade_timeout}); {error, Reason} -> - Transport:fast_close(RawSocket), - exit(Reason) + ok = Transport:fast_close(RawSocket), + exit_on_sock_error(Reason) end. do_init(Parent, Transport, Socket, Options) -> @@ -209,15 +204,10 @@ do_init(Parent, Transport, Socket, Options) -> }, case activate_socket(State) of {ok, NState} -> - recvloop(NState, #{hibernate_after => HibAfterTimeout}); - {error, Reason} when Reason =:= einval; - Reason =:= enotconn; - Reason =:= closed -> - Transport:fast_close(Socket), - exit(normal); + hibernate(NState, #{hibernate_after => HibAfterTimeout}); {error, Reason} -> - Transport:fast_close(Socket), - erlang:exit({shutdown, Reason}) + ok = Transport:fast_close(Socket), + exit_on_sock_error(Reason) end. -compile({inline, [init_limiter/1]}). @@ -225,6 +215,15 @@ init_limiter(undefined) -> undefined; init_limiter({Rate, Burst}) -> esockd_rate_limit:new(Rate, Burst). +exit_on_sock_error(Reason) when Reason =:= einval; + Reason =:= enotconn; + Reason =:= closed -> + erlang:exit(normal); +exit_on_sock_error(timeout) -> + erlang:exit({shutdown, ssl_upgrade_timeout}); +exit_on_sock_error(Reason) -> + erlang:exit({shutdown, Reason}). + %%-------------------------------------------------------------------- %% Recv Loop @@ -291,8 +290,8 @@ handle_msg({Inet, _Sock, Data}, State = #state{channel = Channel}) Oct = iolist_size(Data), emqx_pd:update_counter(incoming_bytes, Oct), ok = emqx_metrics:inc('bytes.received', Oct), - {ok, NChannel} = emqx_channel:handle_in(Oct, Channel), - process_incoming(Data, State#state{channel = NChannel}); + NChannel = emqx_channel:recvd(Oct, Channel), + parse_incoming(Data, State#state{channel = NChannel}); handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State = #state{idle_timer = IdleTimer}) -> @@ -316,24 +315,16 @@ handle_msg({Closed, _Sock}, State) handle_msg({Passive, _Sock}, State) when Passive == tcp_passive; Passive == ssl_passive -> - %% Rate limit and activate socket here. + %% Rate limit here:) NState = ensure_rate_limit(State), - case activate_socket(NState) of - {ok, NState} -> {ok, NState}; - {error, Reason} -> - {ok, {sock_error, Reason}, NState} - end; + handle_info(activate_socket, NState); %% Rate limit timer expired. handle_msg(activate_socket, State) -> NState = State#state{sockstate = idle, limit_timer = undefined }, - case activate_socket(NState) of - {ok, NState} -> {ok, NState}; - {error, Reason} -> - {ok, {sock_error, Reason}, State} - end; + handle_info(activate_socket, NState); handle_msg(Deliver = {deliver, _Topic, _Msg}, State = #state{channel = Channel}) -> @@ -342,7 +333,8 @@ handle_msg(Deliver = {deliver, _Topic, _Msg}, handle_return(Result, State); handle_msg({outgoing, Packets}, State) -> - {ok, handle_outgoing(Packets, State)}; + NState = handle_outgoing(Packets, State), + {ok, NState}; %% something sent handle_msg({inet_reply, _Sock, ok}, _State) -> @@ -362,13 +354,10 @@ handle_msg(Msg, State) -> handle_info(Msg, State). %%-------------------------------------------------------------------- %% Terminate -terminate(Reason, #state{transport = Transport, - socket = Socket, - sockstate = SockSt, - channel = Channel}) -> - ?LOG(debug, "Terminated for ~p", [Reason]), - SockSt =:= closed orelse Transport:fast_close(Socket), +terminate(Reason, State = #state{channel = Channel}) -> + ?LOG(debug, "Terminated due to ~p", [Reason]), emqx_channel:terminate(Reason, Channel), + close_socket(State), exit(Reason). %%-------------------------------------------------------------------- @@ -399,19 +388,19 @@ handle_call(_From, Req, State = #state{channel = Channel}) -> case emqx_channel:handle_call(Req, Channel) of {reply, Reply, NChannel} -> {reply, Reply, State#state{channel = NChannel}}; - {stop, Reason, Reply, NChannel} -> - {stop, Reason, Reply, State#state{channel = NChannel}}; - {stop, Reason, Reply, OutPacket, NChannel} -> + {shutdown, Reason, Reply, NChannel} -> + shutdown(Reason, Reply, State#state{channel = NChannel}); + {shutdown, Reason, Reply, OutPacket, NChannel} -> NState = State#state{channel = NChannel}, NState1 = handle_outgoing(OutPacket, NState), - {stop, Reason, Reply, NState1} + shutdown(Reason, Reply, NState1) end. %%-------------------------------------------------------------------- %% Handle timeout handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) -> - stop(idle_timeout, State); + shutdown(idle_timeout, State); handle_timeout(TRef, emit_stats, State) -> handle_timeout(TRef, {emit_stats, stats(State)}, State); @@ -422,23 +411,19 @@ handle_timeout(TRef, keepalive, State = #state{transport = Transport, {ok, [{recv_oct, RecvOct}]} -> handle_timeout(TRef, {keepalive, RecvOct}, State); {error, Reason} -> - handle_info({sockerr, Reason}, State) + handle_info({sock_error, Reason}, State) end; handle_timeout(TRef, Msg, State = #state{channel = Channel}) -> handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State). %%-------------------------------------------------------------------- -%% Process/Parse incoming data. - --compile({inline, [process_incoming/2]}). -process_incoming(Data, State) -> - {Packets, NState} = parse_incoming(Data, State), - {ok, next_incoming_msgs(Packets), NState}. +%% Parse incoming data -compile({inline, [parse_incoming/2]}). parse_incoming(Data, State) -> - parse_incoming(Data, [], State). + {Packets, NState} = parse_incoming(Data, [], State), + {ok, next_incoming_msgs(Packets), NState}. parse_incoming(<<>>, Packets, State) -> {Packets, State}; @@ -483,12 +468,12 @@ handle_return({ok, NChannel}, State) -> {ok, State#state{channel = NChannel}}; handle_return({ok, Replies, NChannel}, State) -> {ok, next_msgs(Replies), State#state{channel = NChannel}}; -handle_return({stop, Reason, NChannel}, State) -> - stop(Reason, State#state{channel = NChannel}); -handle_return({stop, Reason, OutPacket, NChannel}, State) -> +handle_return({shutdown, Reason, NChannel}, State) -> + shutdown(Reason, State#state{channel = NChannel}); +handle_return({shutdown, Reason, OutPacket, NChannel}, State) -> NState = State#state{channel = NChannel}, NState1 = handle_outgoing(OutPacket, NState), - stop(Reason, NState1). + shutdown(Reason, NState1). %%-------------------------------------------------------------------- %% Handle outgoing packets @@ -522,8 +507,7 @@ send(IoData, State = #state{transport = Transport, ok = emqx_metrics:inc('bytes.sent', Oct), case Transport:async_send(Socket, IoData) of ok -> - {ok, NChannel} = emqx_channel:handle_out(Oct, Channel), - State#state{channel = NChannel}; + State#state{channel = emqx_channel:sent(Oct, Channel)}; Error = {error, _Reason} -> %% Simulate an inet_reply to postpone handling the error self() ! {inet_reply, Socket, Error}, State @@ -542,8 +526,16 @@ handle_info({enter, _}, State = #state{active_n = ActiveN, Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), handle_info({register, Attrs, stats(State)}, State); -handle_info({sockerr, _Reason}, #state{sockstate = closed}) -> ok; -handle_info({sockerr, Reason}, State) -> +handle_info(activate_socket, State) -> + case activate_socket(State) of + {ok, NState} -> {ok, NState}; + {error, Reason} -> + handle_info({sock_error, Reason}, State) + end; + +%%TODO: this is not right +handle_info({sock_error, _Reason}, #state{sockstate = closed}) -> ok; +handle_info({sock_error, Reason}, State) -> ?LOG(debug, "Socket error: ~p", [Reason]), handle_info({sock_closed, Reason}, close_socket(State)); @@ -578,6 +570,8 @@ activate_socket(State = #state{transport = Transport, %%-------------------------------------------------------------------- %% Close Socket +close_socket(State = #state{sockstate = closed}) -> + State; close_socket(State = #state{transport = Transport, socket = Socket}) -> ok = Transport:fast_close(Socket), State#state{sockstate = closed}. @@ -641,7 +635,16 @@ next_msgs(Action) when is_tuple(Action) -> next_msgs(Actions) when is_list(Actions) -> Actions. +shutdown(Reason, State) -> + stop({shutdown, Reason}, State). + +shutdown(Reason, Reply, State) -> + stop({shutdown, Reason}, Reply, State). + -compile({inline, [stop/2]}). stop(Reason, State) -> {stop, Reason, State}. +stop(Reason, Reply, State) -> + {stop, Reason, Reply, State}. + diff --git a/src/emqx_session.erl b/src/emqx_session.erl index a66118752..2b99d888b 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -50,6 +50,11 @@ -logger_header("[Session]"). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + -export([init/2]). -export([ info/1 @@ -58,9 +63,6 @@ , stats/1 ]). -%% Exports for unit tests --export([set_field/3]). - -export([ subscribe/4 , unsubscribe/3 ]). @@ -84,6 +86,9 @@ -export([expire/2]). +%% export for ct +-export([set_field/3]). + -export_type([session/0]). -import(emqx_zone, [get_env/3]). diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 10198b5a8..8b78961f3 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -24,6 +24,11 @@ -logger_header("[MQTT/WS]"). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + %% API -export([ info/1 , stats/1 @@ -188,8 +193,8 @@ websocket_handle({binary, Data}, State = #state{channel = Channel}) -> ?LOG(debug, "RECV ~p", [Data]), Oct = iolist_size(Data), ok = inc_recv_stats(1, Oct), - {ok, NChannel} = emqx_channel:handle_in(Oct, Channel), - process_incoming(Data, State#state{channel = NChannel}); + {ok, NChannel} = emqx_channel:recvd(Oct, Channel), + parse_incoming(Data, State#state{channel = NChannel}); %% Pings should be replied with pongs, cowboy does it automatically %% Pongs can be safely ignored. Clause here simply prevents crash. @@ -282,12 +287,6 @@ handle_call(From, Req, State = #state{channel = Channel}) -> stop(Reason, enqueue(OutPacket, NState)) end. -%%-------------------------------------------------------------------- -%% Handle timeout - -handle_timeout(TRef, Msg, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State). - %%-------------------------------------------------------------------- %% Handle Info @@ -302,18 +301,24 @@ handle_info(Info, State = #state{channel = Channel}) -> handle_return(emqx_channel:handle_info(Info, Channel), State). %%-------------------------------------------------------------------- -%% Process incoming data +%% Handle timeout -process_incoming(<<>>, State) -> +handle_timeout(TRef, Msg, State = #state{channel = Channel}) -> + handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State). + +%%-------------------------------------------------------------------- +%% Parse incoming data + +parse_incoming(<<>>, State) -> {ok, State}; -process_incoming(Data, State = #state{parse_state = ParseState}) -> +parse_incoming(Data, State = #state{parse_state = ParseState}) -> try emqx_frame:parse(Data, ParseState) of {more, NParseState} -> {ok, State#state{parse_state = NParseState}}; {ok, Packet, Rest, NParseState} -> self() ! {incoming, Packet}, - process_incoming(Rest, State#state{parse_state = NParseState}) + parse_incoming(Rest, State#state{parse_state = NParseState}) catch error:Reason:Stk -> ?LOG(error, "~nParse failed for ~p~nStacktrace: ~p~nFrame data: ~p", @@ -343,9 +348,9 @@ handle_return({ok, NChannel}, State) -> reply(State#state{channel= NChannel}); handle_return({ok, Replies, NChannel}, State) -> reply(Replies, State#state{channel= NChannel}); -handle_return({stop, Reason, NChannel}, State) -> +handle_return({shutdown, Reason, NChannel}, State) -> stop(Reason, State#state{channel = NChannel}); -handle_return({stop, Reason, OutPacket, NChannel}, State) -> +handle_return({shutdown, Reason, OutPacket, NChannel}, State) -> NState = State#state{channel = NChannel}, stop(Reason, enqueue(OutPacket, NState)). @@ -356,7 +361,7 @@ handle_outgoing(Packets, State = #state{channel = Channel}) -> IoData = lists:map(serialize_and_inc_stats_fun(State), Packets), Oct = iolist_size(IoData), ok = inc_sent_stats(length(Packets), Oct), - {ok, NChannel} = emqx_channel:handle_out(Oct, Channel), + NChannel = emqx_channel:sent(Oct, Channel), {{binary, IoData}, State#state{channel = NChannel}}. %% TODO: Duplicated with emqx_channel:serialize_and_inc_stats_fun/1 diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index 6e73d0703..8df85bea3 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -45,19 +45,43 @@ all() -> emqx_ct:all(?MODULE). +%%-------------------------------------------------------------------- +%% CT Callbacks +%%-------------------------------------------------------------------- + init_per_suite(Config) -> - emqx_ct_helpers:boot_modules([router, broker]), - emqx_ct_helpers:start_apps([]), Config. end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). + ok. %%-------------------------------------------------------------------- -%% Test cases for handle_in +%% Test cases for channel info/stats/caps %%-------------------------------------------------------------------- -t_handle_connect(_) -> +t_chan_info(_) -> error('TODO'). + +t_chan_attrs(_) -> error('TODO'). + +t_chan_stats(_) -> error('TODO'). + +t_chan_caps(_) -> error('TODO'). + +t_chan_recvd(_) -> error('TODO'). + +t_chan_sent(_) -> error('TODO'). + +%%-------------------------------------------------------------------- +%% Test cases for channel init +%%-------------------------------------------------------------------- + +t_chan_init(_) -> error('TODO'). + +%%-------------------------------------------------------------------- +%% Test cases for channel handle_in +%%-------------------------------------------------------------------- + +t_handle_in_connect_packet(_) -> ConnPkt = #mqtt_packet_connect{ proto_name = <<"MQTT">>, proto_ver = ?MQTT_PROTO_V4, @@ -69,35 +93,35 @@ t_handle_connect(_) -> username = <<"username">>, password = <<"passwd">> }, - with_channel( - fun(Channel) -> - ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}), - ExpectedOutput = [{enter, connected},{outgoing, ConnAck}], - {ok, Output, Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel), - ?assertEqual(ExpectedOutput, Output), - #{clientid := ClientId, username := Username} = emqx_channel:info(clientinfo, Channel1), - ?assertEqual(<<"clientid">>, ClientId), - ?assertEqual(<<"username">>, Username) - end). + with_chan(fun(Channel) -> + ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}), + ExpectedOutput = [{enter, connected},{outgoing, ConnAck}], + {ok, Output, Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel), + ?assertEqual(ExpectedOutput, Output), + #{clientid := ClientId, username := Username} = emqx_channel:info(clientinfo, Channel1), + ?assertEqual(<<"clientid">>, ClientId), + ?assertEqual(<<"username">>, Username) + end). -t_handle_in_publish_qos0(_) -> - with_channel( - fun(Channel) -> - Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), - {ok, Channel1} = handle_in(Publish, Channel), - ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, Channel1)) - end). +t_handle_in_unexpected_connect_packet(_) -> + error('TODO'). -t_handle_in_publish_qos1(_) -> - with_channel( - fun(Channel) -> - Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), - {ok, ?PUBACK_PACKET(1, RC), _} = handle_in(Publish, Channel), - ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)) - end). +t_handle_in_qos0_publish(_) -> + with_chan(fun(Channel) -> + Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), + {ok, Channel1} = handle_in(Publish, Channel), + ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, Channel1)) + end). -t_handle_publish_qos2(_) -> - with_channel( +t_handle_in_qos1_publish(_) -> + with_chan(fun(Channel) -> + Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), + {ok, ?PUBACK_PACKET(1, RC), _} = handle_in(Publish, Channel), + ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)) + end). + +t_handle_in_qos2_publish(_) -> + with_chan( fun(Channel) -> Publish1 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>), {ok, ?PUBREC_PACKET(1, RC), Channel1} = handle_in(Publish1, Channel), @@ -109,14 +133,14 @@ t_handle_publish_qos2(_) -> end). t_handle_in_puback(_) -> - with_channel( + with_chan( fun(Channel) -> {ok, Channel1} = handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel), ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel1)) end). t_handle_in_pubrec(_) -> - with_channel( + with_chan( fun(Channel) -> {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} = handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel), @@ -124,7 +148,7 @@ t_handle_in_pubrec(_) -> end). t_handle_in_pubrel(_) -> - with_channel( + with_chan( fun(Channel) -> {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} = handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel), @@ -132,14 +156,14 @@ t_handle_in_pubrel(_) -> end). t_handle_in_pubcomp(_) -> - with_channel( + with_chan( fun(Channel) -> {ok, Channel1} = handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)) end). -t_handle_subscribe(_) -> - with_channel( +t_handle_in_subscribe(_) -> + with_chan( fun(Channel) -> TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], {ok, ?SUBACK_PACKET(10, [?QOS_0]), Channel1} @@ -149,55 +173,68 @@ t_handle_subscribe(_) -> ?assertEqual(maps:from_list(TopicFilters), Subscriptions) end). -t_handle_unsubscribe(_) -> - with_channel( +t_handle_in_unsubscribe(_) -> + with_chan( fun(Channel) -> {ok, ?UNSUBACK_PACKET(11), Channel} = handle_in(?UNSUBSCRIBE_PACKET(11, #{}, [<<"+">>]), Channel) end). -t_handle_pingreq(_) -> - with_channel( - fun(Channel) -> - {ok, ?PACKET(?PINGRESP), Channel} = handle_in(?PACKET(?PINGREQ), Channel) - end). +t_handle_in_pingreq(_) -> + with_chan(fun(Channel) -> + {ok, ?PACKET(?PINGRESP), Channel} = handle_in(?PACKET(?PINGREQ), Channel) + end). -t_handle_disconnect(_) -> - with_channel( - fun(Channel) -> - {stop, {shutdown, normal}, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel), - ?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)) - end). +t_handle_in_disconnect(_) -> + with_chan(fun(Channel) -> + {stop, {shutdown, normal}, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel), + ?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)) + end). t_handle_in_auth(_) -> - with_channel( - fun(Channel) -> - Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR), - {stop, {shutdown, implementation_specific_error}, Packet, Channel} = handle_in(?AUTH_PACKET(), Channel) - end). + with_chan(fun(Channel) -> + Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR), + {stop, {shutdown, implementation_specific_error}, Packet, Channel} = handle_in(?AUTH_PACKET(), Channel) + end). -%%-------------------------------------------------------------------- -%% Test cases for handle_deliver -%%-------------------------------------------------------------------- +t_handle_in_frame_error(_) -> + with_chan(fun(Channel) -> error('TODO') end). -t_handle_deliver(_) -> - with_connected_channel( - fun(Channel) -> - TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS#{qos => ?QOS_2}}], - {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel1} - = handle_in(?SUBSCRIBE_PACKET(1, #{}, TopicFilters), Channel), - Msg0 = emqx_message:make(<<"clientx">>, ?QOS_0, <<"t0">>, <<"qos0">>), - Msg1 = emqx_message:make(<<"clientx">>, ?QOS_1, <<"t1">>, <<"qos1">>), - Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}], - {ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, Channel1), - ?assertEqual([?QOS_0, ?QOS_1], [emqx_packet:qos(Pkt)|| Pkt <- Packets]) - end). +t_handle_in_expected_packet(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_process_connect(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_handle_publish(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_process_publish(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_process_subscribe(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_process_unsubscribe(_) -> + with_chan(fun(Channel) -> error('TODO') end). %%-------------------------------------------------------------------- %% Test cases for handle_out %%-------------------------------------------------------------------- -t_handle_out_connack(_) -> +t_handle_out_delivers(_) -> + with_chan(fun(Channel) -> + TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS#{qos => ?QOS_2}}], + {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel1} + = handle_in(?SUBSCRIBE_PACKET(1, #{}, TopicFilters), Channel), + Msg0 = emqx_message:make(<<"clientx">>, ?QOS_0, <<"t0">>, <<"qos0">>), + Msg1 = emqx_message:make(<<"clientx">>, ?QOS_1, <<"t1">>, <<"qos1">>), + Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}], + {ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, Channel1), + ?assertEqual([?QOS_0, ?QOS_1], [emqx_packet:qos(Pkt)|| Pkt <- Packets]) + end). + +t_handle_out_connack_sucess(_) -> ConnPkt = #mqtt_packet_connect{ proto_name = <<"MQTT">>, proto_ver = ?MQTT_PROTO_V4, @@ -205,97 +242,209 @@ t_handle_out_connack(_) -> properties = #{}, clientid = <<"clientid">> }, - with_channel( - fun(Channel) -> - {ok, [{enter, connected},{outgoing, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan} - = handle_out({connack, ?RC_SUCCESS, 0, ConnPkt}, Channel), - {stop, {shutdown, not_authorized}, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} - = handle_out({connack, ?RC_NOT_AUTHORIZED, ConnPkt}, Channel) - end). + with_chan(fun(Channel) -> + {ok, [{enter, connected},{outgoing, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan} + = handle_out({connack, ?RC_SUCCESS, 0, ConnPkt}, Channel), + {stop, {shutdown, not_authorized}, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} + = handle_out({connack, ?RC_NOT_AUTHORIZED, ConnPkt}, Channel) + end). + +t_handle_out_connack_failure(_) -> + with_chan(fun(Channel) -> error('TODO') end). t_handle_out_publish(_) -> - with_channel( - fun(Channel) -> - Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)}, - Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, - {ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel), - {ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel), - {ok, {outgoing, Packets}, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel), - ?assertEqual(2, length(Packets)), - ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1)) - end). + with_chan(fun(Channel) -> + Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)}, + Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, + {ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel), + {ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel), + {ok, {outgoing, Packets}, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel), + ?assertEqual(2, length(Packets)), + ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1)) + end). t_handle_out_puback(_) -> - with_channel( - fun(Channel) -> - {ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1} - = handle_out({puback, 1, ?RC_SUCCESS}, Channel), - ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + with_chan(fun(Channel) -> + {ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel), + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1} + = handle_out({puback, 1, ?RC_SUCCESS}, Channel), + ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1)) + end). t_handle_out_pubrec(_) -> - with_channel( - fun(Channel) -> - {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1} - = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel), - ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + with_chan(fun(Channel) -> + {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1} + = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel), + ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1)) + end). t_handle_out_pubrel(_) -> - with_channel( - fun(Channel) -> - {ok, ?PUBREL_PACKET(2), Channel1} - = handle_out({pubrel, 2, ?RC_SUCCESS}, Channel), - {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2} - = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1), - ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)) - end). + with_chan(fun(Channel) -> + {ok, ?PUBREL_PACKET(2), Channel1} + = handle_out({pubrel, 2, ?RC_SUCCESS}, Channel), + {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2} + = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1), + ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)) + end). t_handle_out_pubcomp(_) -> - with_channel( - fun(Channel) -> - {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1} - = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel), - ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + with_chan(fun(Channel) -> + {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1} + = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel), + ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) + end). t_handle_out_suback(_) -> - with_channel( - fun(Channel) -> - {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel} - = handle_out({suback, 1, [?QOS_2]}, Channel) - end). + with_chan(fun(Channel) -> + {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel} + = handle_out({suback, 1, [?QOS_2]}, Channel) + end). t_handle_out_unsuback(_) -> - with_channel( - fun(Channel) -> - {ok, ?UNSUBACK_PACKET(1), Channel} - = handle_out({unsuback, 1, [?RC_SUCCESS]}, Channel) - end). + with_chan(fun(Channel) -> + {ok, ?UNSUBACK_PACKET(1), Channel} + = handle_out({unsuback, 1, [?RC_SUCCESS]}, Channel) + end). t_handle_out_disconnect(_) -> - with_channel( + with_chan( fun(Channel) -> handle_out({disconnect, ?RC_SUCCESS}, Channel) end). +t_handle_out_unexpected(_) -> + with_chan(fun(Channel) -> + handle_out({disconnect, ?RC_SUCCESS}, Channel) + end). + +%%-------------------------------------------------------------------- +%% Test cases for handle_call +%%-------------------------------------------------------------------- + +t_handle_call_kick(_) -> + error('TODO'). + +t_handle_call_discard(_) -> + error('TODO'). + +t_handle_call_takeover(_) -> + error('TODO'). + +t_handle_call_unexpected(_) -> + error('TODO'). + +%%-------------------------------------------------------------------- +%% Test cases for handle_info +%%-------------------------------------------------------------------- + +t_handle_info_subscribe(_) -> + error('TODO'). + +t_handle_info_unsubscribe(_) -> + error('TODO'). + +t_handle_info_sock_closed(_) -> + error('TODO'). + %%-------------------------------------------------------------------- %% Test cases for handle_timeout %%-------------------------------------------------------------------- -t_handle_timeout(_) -> - with_channel( - fun(Channel) -> - 'TODO' - end). +t_handle_timeout_emit_stats(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +t_handle_timeout_keepalive(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +t_handle_timeout_retry_delivery(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +t_handle_timeout_expire_awaiting_rel(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +t_handle_timeout_expire_session(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +t_handle_timeout_will_message(_) -> + with_chan(fun(Channel) -> 'TODO' end). + +%%-------------------------------------------------------------------- +%% Test cases for ensure_timer +%%-------------------------------------------------------------------- + +t_ensure_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_reset_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_alive_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_retry_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_await_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_expire_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_will_timer(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +%%-------------------------------------------------------------------- +%% Test cases for internal functions +%%-------------------------------------------------------------------- + +t_enrich_conninfo(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_enrich_client(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_banned(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_flapping(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_auth_connect(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_process_alias(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_pub_acl(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_pub_alias(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_subscribe(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_sub_acl(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_check_sub_caps(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_enrich_subid(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_enrich_subopts(_) -> + with_chan(fun(Channel) -> error('TODO') end). + +t_enrich_caps(_) -> + with_chan(fun(Channel) -> error('TODO') end). %%-------------------------------------------------------------------- %% Test cases for terminate %%-------------------------------------------------------------------- t_terminate(_) -> - with_channel( + with_chan( fun(Channel) -> 'TODO' end). @@ -305,15 +454,15 @@ t_terminate(_) -> %%-------------------------------------------------------------------- with_connected_channel(TestFun) -> - with_channel( + with_chan( fun(Channel) -> TestFun(emqx_channel:set_field(conn_state, connected, Channel)) end). -with_channel(TestFun) -> - with_channel(#{}, TestFun). +with_chan(TestFun) -> + with_chan(#{}, TestFun). -with_channel(ConnInfo, TestFun) -> +with_chan(ConnInfo, TestFun) -> ConnInfo1 = maps:merge(?DEFAULT_CONNINFO, ConnInfo), ClientInfo = #{zone => <<"external">>, protocol => mqtt, diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index 309d2a6b5..4ee48ecde 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -19,57 +19,310 @@ -compile(export_all). -compile(nowarn_export_all). +-include("emqx_mqtt.hrl"). -include_lib("eunit/include/eunit.hrl"). --define(Transport, esockd_transport). +-define(STATS_KYES, [recv_pkt, recv_msg, send_pkt, send_msg, + recv_oct, recv_cnt, send_oct, send_cnt, + send_pend + ]). all() -> emqx_ct:all(?MODULE). +%%-------------------------------------------------------------------- +%% CT callbacks +%%-------------------------------------------------------------------- + init_per_suite(Config) -> - ok = meck:new(esockd_transport, [passthrough, no_history]), - ok = meck:new(emqx_channel, [passthrough, no_history]), Config. end_per_suite(_Config) -> ok. -t_start_link_error(_) -> - process_flag(trap_exit, true), - ok = meck:expect(esockd_transport, wait, fun(_Sock) -> {error, enotconn} end), - ok = meck:expect(esockd_transport, fast_close, fun(_Sock) -> ok end), - {ok, Pid} = emqx_connection:start_link(esockd_transport, socket, []), - timer:sleep(100), - ?assertNot(erlang:is_process_alive(Pid)), - ?assertEqual([{'EXIT', Pid, normal}], proc_mailbox()). +init_per_testcase(_TestCase, Config) -> + %% Meck Transport + ok = meck:new(emqx_transport, [non_strict, passthrough, no_history]), + ok = meck:expect(emqx_transport, wait, fun(Sock) -> {ok, Sock} end), + ok = meck:expect(emqx_transport, type, fun(_Sock) -> tcp end), + ok = meck:expect(emqx_transport, ensure_ok_or_exit, + fun(peername, [sock]) -> {ok, {{127,0,0,1}, 3456}}; + (sockname, [sock]) -> {ok, {{127,0,0,1}, 1883}}; + (peercert, [sock]) -> undefined + end), + ok = meck:expect(emqx_transport, setopts, fun(_Sock, _Opts) -> ok end), + ok = meck:expect(emqx_transport, getstat, fun(_Sock, Options) -> + {ok, [{K, 0} || K <- Options]} + end), + ok = meck:expect(emqx_transport, async_send, fun(_Sock, _Data) -> ok end), + ok = meck:expect(emqx_transport, fast_close, fun(_Sock) -> ok end), + %% Meck Channel + ok = meck:new(emqx_channel, [passthrough, no_history]), + %% Meck Metrics + ok = meck:new(emqx_metrics, [passthrough, no_history]), + ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), + ok = meck:expect(emqx_metrics, inc_recv, fun(_) -> ok end), + ok = meck:expect(emqx_metrics, inc_sent, fun(_) -> ok end), + Config. -todo_t_basic(_) -> - Topic = <<"TopicA">>, - {ok, C} = emqtt:start_link([{port, 1883}, {clientid, <<"hello">>}]), - {ok, _} = emqtt:connect(C), - {ok, _, [1]} = emqtt:subscribe(C, Topic, qos1), - {ok, _, [2]} = emqtt:subscribe(C, Topic, qos2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - ?assertEqual(3, length(recv_msgs(3))), - ok = emqtt:disconnect(C). +end_per_testcase(_TestCase, Config) -> + ok = meck:unload(emqx_transport), + ok = meck:unload(emqx_channel), + ok = meck:unload(emqx_metrics), + Config. -proc_mailbox() -> - proc_mailbox(self()). -proc_mailbox(Pid) -> - {messages, Msgs} = erlang:process_info(Pid, messages), - Msgs. +%%-------------------------------------------------------------------- +%% Test cases +%%-------------------------------------------------------------------- -recv_msgs(Count) -> - recv_msgs(Count, []). +t_start_link_ok(_) -> + with_connection(fun(CPid) -> + state = element(1, sys:get_state(CPid)) + end). -recv_msgs(0, Msgs) -> - Msgs; -recv_msgs(Count, Msgs) -> +t_start_link_exit_on_wait(_) -> + ok = exit_on_wait_error(enotconn, normal), + ok = exit_on_wait_error(einval, normal), + ok = exit_on_wait_error(closed, normal), + ok = exit_on_wait_error(timeout, {shutdown, ssl_upgrade_timeout}), + ok = exit_on_wait_error(enetdown, {shutdown, enetdown}). + +t_start_link_exit_on_activate(_) -> + ok = exit_on_activate_error(enotconn, normal), + ok = exit_on_activate_error(einval, normal), + ok = exit_on_activate_error(closed, normal), + ok = exit_on_activate_error(econnreset, {shutdown, econnreset}). + +t_get_conn_info(_) -> + with_connection(fun(CPid) -> + #{sockinfo := SockInfo} = emqx_connection:info(CPid), + ?assertEqual(#{active_n => 100, + peername => {{127,0,0,1},3456}, + pub_limit => undefined, + rate_limit => undefined, + sockname => {{127,0,0,1},1883}, + sockstate => running, + socktype => tcp}, SockInfo) + end). + +t_get_conn_stats(_) -> + with_connection(fun(CPid) -> + Stats = emqx_connection:stats(CPid), + lists:foreach(fun(Key) -> + 0 = proplists:get_value(Key, Stats) + end, ?STATS_KYES) + end). + +t_handle_call_discard(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_call, + fun(discard, Channel) -> + {shutdown, discarded, ok, Channel} + end), + ok = emqx_connection:call(CPid, discard), + timer:sleep(100), + ok = trap_exit(CPid, {shutdown, discarded}) + end, #{trap_exit => true}). + +t_handle_call_takeover(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_call, + fun({takeover, 'begin'}, Channel) -> + {reply, session, Channel}; + ({takeover, 'end'}, Channel) -> + {shutdown, takeovered, [], Channel} + end), + session = emqx_connection:call(CPid, {takeover, 'begin'}), + [] = emqx_connection:call(CPid, {takeover, 'end'}), + timer:sleep(100), + ok = trap_exit(CPid, {shutdown, takeovered}) + end, #{trap_exit => true}). + +t_handle_call_any(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_call, + fun(_Req, Channel) -> {reply, ok, Channel} end), + ok = emqx_connection:call(CPid, req) + end). + +t_handle_incoming_connect(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end), + ConnPkt = #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, + proto_name = <<"MQTT">>, + clientid = <<>>, + clean_start = true, + keepalive = 60 + }, + Frame = make_frame(?CONNECT_PACKET(ConnPkt)), + CPid ! {tcp, sock, Frame} + end). + +t_handle_incoming_publish(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end), + Frame = make_frame(?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>)), + CPid ! {tcp, sock, Frame} + end). + +t_handle_incoming_subscribe(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end), + Frame = <>, + CPid ! {tcp, sock, Frame} + end). + +t_handle_incoming_unsubscribe(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end), + Frame = <>, + CPid ! {tcp, sock, Frame} + end). + +t_handle_sock_error(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_info, + fun({_, Reason}, Channel) -> + {shutdown, Reason, Channel} + end), + %% TODO: fixme later + CPid ! {tcp_error, sock, econnreset}, + timer:sleep(100), + trap_exit(CPid, {shutdown, econnreset}) + end, #{trap_exit => true}). + +t_handle_sock_passive(_) -> + with_connection(fun(CPid) -> CPid ! {tcp_passive, sock} end). + +t_handle_sock_activate(_) -> + with_connection(fun(CPid) -> CPid ! activate_socket end). + +t_handle_sock_closed(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_info, + fun({sock_closed, Reason}, Channel) -> + {shutdown, Reason, Channel} + end), + CPid ! {tcp_closed, sock}, + timer:sleep(100), + %%TODO: closed? + trap_exit(CPid, {shutdown, closed}) + end, #{trap_exit => true}). + +t_handle_outgoing(_) -> + with_connection(fun(CPid) -> + Publish = ?PUBLISH_PACKET(?QOS_2, <<"Topic">>, 1, <<>>), + CPid ! {outgoing, Publish}, + CPid ! {outgoing, ?PUBREL_PACKET(1)}, + CPid ! {outgoing, [?PUBCOMP_PACKET(1)]} + end). + +t_conn_rate_limit(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_, Channel) -> {ok, Channel} end), + lists:foreach(fun(I) -> + Publish = ?PUBLISH_PACKET(?QOS_0, <<"Topic">>, I, payload(2000)), + CPid ! {tcp, sock, make_frame(Publish)} + end, [1, 2]) + %%#{sockinfo := #{sockstate := blocked}} = emqx_connection:info(CPid) + end, #{active_n => 1, rate_limit => {1, 1024}}). + +t_conn_pub_limit(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_in, fun(_, Channel) -> {ok, Channel} end), + ok = lists:foreach(fun(I) -> + CPid ! {incoming, ?PUBLISH_PACKET(?QOS_0, <<"Topic">>, I, <<>>)} + end, lists:seq(1, 3)) + %%#{sockinfo := #{sockstate := blocked}} = emqx_connection:info(CPid) + end, #{active_n => 1, publish_limit => {1, 2}}). + +t_oom_shutdown(_) -> + with_connection(fun(CPid) -> + CPid ! {shutdown, message_queue_too_long}, + timer:sleep(100), + trap_exit(CPid, {shutdown, message_queue_too_long}) + end, #{trap_exit => true}). + +t_handle_idle_timeout(_) -> + ok = emqx_zone:set_env(external, idle_timeout, 10), + with_connection(fun(CPid) -> + timer:sleep(100), + trap_exit(CPid, {shutdown, idle_timeout}) + end, #{zone => external, trap_exit => true}). + +t_handle_emit_stats(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_timeout, + fun(_TRef, _TMsg, Channel) -> + {ok, Channel} + end), + CPid ! {timeout, make_ref(), emit_stats} + end). + +t_handle_keepalive_timeout(_) -> + with_connection(fun(CPid) -> + ok = meck:expect(emqx_channel, handle_timeout, + fun(_TRef, _TMsg, Channel) -> + {shutdown, keepalive_timeout, Channel} + end), + CPid ! {timeout, make_ref(), keepalive}, + timer:sleep(100), + trap_exit(CPid, {shutdown, keepalive_timeout}) + end, #{trap_exit => true}). + +t_handle_shutdown(_) -> + with_connection(fun(CPid) -> + CPid ! Shutdown = {shutdown, reason}, + timer:sleep(100), + trap_exit(CPid, Shutdown) + end, #{trap_exit => true}). + +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +exit_on_wait_error(SockErr, Reason) -> + ok = meck:expect(emqx_transport, wait, + fun(_Sock) -> + {error, SockErr} + end), + with_connection(fun(CPid) -> + timer:sleep(100), + trap_exit(CPid, Reason) + end, #{trap_exit => true}). + +exit_on_activate_error(SockErr, Reason) -> + ok = meck:expect(emqx_transport, setopts, + fun(_Sock, _Opts) -> + {error, SockErr} + end), + with_connection(fun(CPid) -> + timer:sleep(100), + trap_exit(CPid, Reason) + end, #{trap_exit => true}). + +with_connection(TestFun) -> + with_connection(TestFun, #{trap_exit => false}). + +with_connection(TestFun, Options) when is_map(Options) -> + with_connection(TestFun, maps:to_list(Options)); +with_connection(TestFun, Options) -> + TrapExit = proplists:get_value(trap_exit, Options, false), + process_flag(trap_exit, TrapExit), + {ok, CPid} = emqx_connection:start_link(emqx_transport, sock, Options), + TestFun(CPid), + TrapExit orelse emqx_connection:stop(CPid), + ok. + +trap_exit(Pid, Reason) -> receive - {publish, Msg} -> - recv_msgs(Count-1, [Msg|Msgs]) - after 100 -> - Msgs + {'EXIT', Pid, Reason} -> ok; + {'EXIT', Pid, Other} -> error({unexpect_exit, Other}) + after + 0 -> error({expect_exit, Reason}) end. +make_frame(Packet) -> + iolist_to_binary(emqx_frame:serialize(Packet)). + +payload(Len) -> iolist_to_binary(lists:duplicate(Len, 1)). + diff --git a/test/emqx_session_SUITE.erl b/test/emqx_session_SUITE.erl index e58c4b859..dfa5da614 100644 --- a/test/emqx_session_SUITE.erl +++ b/test/emqx_session_SUITE.erl @@ -23,304 +23,81 @@ -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). --define(mock_modules, - [ emqx_metrics - , emqx_broker - , emqx_misc - , emqx_message - , emqx_hooks - , emqx_zone - ]). - all() -> emqx_ct:all(?MODULE). -t_proper_session(_) -> - Opts = [{numtests, 100}, {to_file, user}], - ok = emqx_logger:set_log_level(emergency), - ok = before_proper(), - ?assert(proper:quickcheck(prop_session(), Opts)), - ok = after_proper(). +t_session_init(_) -> + error('TODO'). -before_proper() -> - load(?mock_modules). +%%-------------------------------------------------------------------- +%% Test cases for info/stats +%%-------------------------------------------------------------------- -after_proper() -> - unload(?mock_modules), - emqx_logger:set_log_level(error). +t_session_info(_) -> + error('TODO'). -prop_session() -> - ?FORALL({Session, OpList}, {session(), session_op_list()}, - begin - try - apply_ops(Session, OpList), - true - after - true - end - end). +t_session_attrs(_) -> + error('TODO'). -%%%%%%%%%%%%%%% -%%% Helpers %%% -%%%%%%%%%%%%%%% +t_session_stats(_) -> + error('TODO'). -apply_ops(Session, []) -> - ?assertEqual(session, element(1, Session)); -apply_ops(Session, [Op | Rest]) -> - NSession = apply_op(Session, Op), - apply_ops(NSession, Rest). +%%-------------------------------------------------------------------- +%% Test cases for pub/sub +%%-------------------------------------------------------------------- -apply_op(Session, info) -> - Info = emqx_session:info(Session), - ?assert(is_map(Info)), - ?assert(maps:size(Info) > 0), - Session; -apply_op(Session, attrs) -> - Attrs = emqx_session:attrs(Session), - ?assert(is_map(Attrs)), - ?assert(maps:size(Attrs) > 0), - Session; -apply_op(Session, stats) -> - Stats = emqx_session:stats(Session), - ?assert(is_list(Stats)), - ?assert(length(Stats) > 0), - Session; -apply_op(Session, {info, InfoArg}) -> - _Ret = emqx_session:info(InfoArg, Session), - Session; -apply_op(Session, {subscribe, {Client, TopicFilter, SubOpts}}) -> - case emqx_session:subscribe(Client, TopicFilter, SubOpts, Session) of - {ok, NSession} -> - NSession; - {error, ?RC_QUOTA_EXCEEDED} -> - Session - end; -apply_op(Session, {unsubscribe, {Client, TopicFilter}}) -> - case emqx_session:unsubscribe(Client, TopicFilter, Session) of - {ok, NSession} -> - NSession; - {error, ?RC_NO_SUBSCRIPTION_EXISTED} -> - Session - end; -apply_op(Session, {publish, {PacketId, Msg}}) -> - case emqx_session:publish(PacketId, Msg, Session) of - {ok, _Msg} -> - Session; - {ok, _Deliver, NSession} -> - NSession; - {error, _ErrorCode} -> - Session - end; -apply_op(Session, {puback, PacketId}) -> - case emqx_session:puback(PacketId, Session) of - {ok, _Msg, NSession} -> - NSession; - {ok, _Msg, _Publishes, NSession} -> - NSession; - {error, _ErrorCode} -> - Session - end; -apply_op(Session, {pubrec, PacketId}) -> - case emqx_session:pubrec(PacketId, Session) of - {ok, _Msg, NSession} -> - NSession; - {error, _ErrorCode} -> - Session - end; -apply_op(Session, {pubrel, PacketId}) -> - case emqx_session:pubrel(PacketId, Session) of - {ok, NSession} -> - NSession; - {error, _ErrorCode} -> - Session - end; -apply_op(Session, {pubcomp, PacketId}) -> - case emqx_session:pubcomp(PacketId, Session) of - {ok, _Msgs} -> - Session; - {ok, _Msgs, NSession} -> - NSession; - {error, _ErrorCode} -> - Session - end; -apply_op(Session, {deliver, Delivers}) -> - {ok, _Msgs, NSession} = emqx_session:deliver(Delivers, Session), - NSession. +t_subscribe(_) -> + error('TODO'). -%%%%%%%%%%%%%%%%%% -%%% Generators %%% -%%%%%%%%%%%%%%%%%% -session_op_list() -> - Union = [info, - attrs, - stats, - {info, info_args()}, - {subscribe, sub_args()}, - {unsubscribe, unsub_args()}, - {publish, publish_args()}, - {puback, puback_args()}, - {pubrec, pubrec_args()}, - {pubrel, pubrel_args()}, - {pubcomp, pubcomp_args()}, - {deliver, deliver_args()} - ], - list(?LAZY(oneof(Union))). +t_unsubscribe(_) -> + error('TODO'). -deliver_args() -> - list({deliver, topic(), message()}). +t_publish_qos0(_) -> + error('TODO'). -info_args() -> - oneof([subscriptions, - subscriptions_max, - upgrade_qos, - inflight, - inflight_max, - retry_interval, - mqueue_len, - mqueue_max, - mqueue_dropped, - next_pkt_id, - awaiting_rel, - awaiting_rel_max, - await_rel_timeout, - created_at - ]). +t_publish_qos1(_) -> + error('TODO'). -sub_args() -> - ?LET({ClientId, TopicFilter, SubOpts}, - {clientid(), topic(), sub_opts()}, - {#{clientid => ClientId}, TopicFilter, SubOpts}). +t_publish_qos2(_) -> + error('TODO'). -unsub_args() -> - ?LET({ClientId, TopicFilter}, - {clientid(), topic()}, - {#{clientid => ClientId}, TopicFilter}). +t_puback(_) -> + error('TODO'). -publish_args() -> - ?LET({PacketId, Message}, - {packetid(), message()}, - {PacketId, Message}). +t_pubrec(_) -> + error('TODO'). -puback_args() -> - packetid(). +t_pubrel(_) -> + error('TODO'). -pubrec_args() -> - packetid(). +t_pubcomp(_) -> + error('TODO'). -pubrel_args() -> - packetid(). +%%-------------------------------------------------------------------- +%% Test cases for deliver/retry +%%-------------------------------------------------------------------- -pubcomp_args() -> - packetid(). +t_deliver(_) -> + error('TODO'). -sub_opts() -> - ?LET({RH, RAP, NL, QOS, SHARE, SUBID}, - {rh(), rap(), nl(), qos(), share(), subid()} - , make_subopts(RH, RAP, NL, QOS, SHARE, SUBID)). +t_enqueue(_) -> + error('TODO'). -message() -> - ?LET({QoS, Topic, Payload}, - {qos(), topic(), payload()}, - emqx_message:make(proper, QoS, Topic, Payload)). +t_retry(_) -> + error('TODO'). -subid() -> integer(). +%%-------------------------------------------------------------------- +%% Test cases for takeover/resume +%%-------------------------------------------------------------------- -rh() -> oneof([0, 1, 2]). +t_takeover(_) -> + error('TODO'). -rap() -> oneof([0, 1]). +t_resume(_) -> + error('TODO'). -nl() -> oneof([0, 1]). +t_redeliver(_) -> + error('TODO'). -qos() -> oneof([0, 1, 2]). - -share() -> binary(). - -clientid() -> binary(). - -topic() -> ?LET(No, choose(1, 10), - begin - NoBin = integer_to_binary(No), - <<"topic/", NoBin/binary>> - end). - -payload() -> binary(). - -packetid() -> choose(1, 30). - -zone() -> - ?LET(Zone, [{max_subscriptions, max_subscription()}, - {upgrade_qos, upgrade_qos()}, - {retry_interval, retry_interval()}, - {max_awaiting_rel, max_awaiting_rel()}, - {await_rel_timeout, await_rel_timeout()}] - , maps:from_list(Zone)). - -max_subscription() -> - frequency([{33, 0}, - {33, 1}, - {34, choose(0,10)}]). - -upgrade_qos() -> bool(). - -retry_interval() -> ?LET(Interval, choose(0, 20), Interval*1000). - -max_awaiting_rel() -> choose(0, 10). - -await_rel_timeout() -> ?LET(Interval, choose(0, 150), Interval*1000). - -max_inflight() -> choose(0, 10). - -option() -> - ?LET(Option, [{receive_maximum , max_inflight()}], - maps:from_list(Option)). - -session() -> - ?LET({Zone, Options}, - {zone(), option()}, - begin - Session = emqx_session:init(#{zone => Zone}, Options), - emqx_session:set_field(next_pkt_id, 16#ffff, Session) - end). - -%%%%%%%%%%%%%%%%%%%%%%%%%% -%%% Internal functions %%% -%%%%%%%%%%%%%%%%%%%%%%%%%% - -make_subopts(RH, RAP, NL, QOS, SHARE, SubId) -> - #{rh => RH, - rap => RAP, - nl => NL, - qos => QOS, - share => SHARE, - subid => SubId}. - - -load(Modules) -> - [mock(Module) || Module <- Modules], - ok. - -unload(Modules) -> - lists:foreach(fun(Module) -> - ok = meck:unload(Module) - end, Modules). - -mock(Module) -> - ok = meck:new(Module, [passthrough, no_history]), - do_mock(Module). - -do_mock(emqx_metrics) -> - meck:expect(emqx_metrics, inc, fun(_Anything) -> ok end); -do_mock(emqx_broker) -> - meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end), - meck:expect(emqx_broker, set_subopts, fun(_, _) -> ok end), - meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end), - meck:expect(emqx_broker, publish, fun(_) -> ok end); -do_mock(emqx_misc) -> - meck:expect(emqx_misc, start_timer, fun(_, _) -> tref end); -do_mock(emqx_message) -> - meck:expect(emqx_message, set_header, fun(_Hdr, _Val, Msg) -> Msg end), - meck:expect(emqx_message, is_expired, fun(_Msg) -> (rand:uniform(16) > 8) end); -do_mock(emqx_hooks) -> - meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end); -do_mock(emqx_zone) -> - meck:expect(emqx_zone, get_env, fun(Env, Key, Default) -> maps:get(Key, Env, Default) end). +t_expire(_) -> + error('TODO'). diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl index 51b9fde8d..fa036335f 100644 --- a/test/emqx_ws_connection_SUITE.erl +++ b/test/emqx_ws_connection_SUITE.erl @@ -16,42 +16,204 @@ -module(emqx_ws_connection_SUITE). +-include("emqx_mqtt.hrl"). +-include_lib("eunit/include/eunit.hrl"). + -compile(export_all). -compile(nowarn_export_all). --include_lib("eunit/include/eunit.hrl"). +-import(emqx_ws_connection, + [ websocket_handle/2 + , websocket_info/2 + ]). + +-define(STATS_KEYS, [recv_oct, recv_cnt, send_oct, send_cnt, + recv_pkt, recv_msg, send_pkt, send_msg + ]). all() -> emqx_ct:all(?MODULE). +%%-------------------------------------------------------------------- +%% CT callbacks +%%-------------------------------------------------------------------- + init_per_suite(Config) -> - emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([]), Config. end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). + ok. -t_basic(_) -> - Topic = <<"TopicA">>, - {ok, C} = emqtt:start_link([{host, "127.0.0.1"}, {port, 8083}]), - {ok, _} = emqtt:ws_connect(C), - {ok, _, [1]} = emqtt:subscribe(C, Topic, qos1), - {ok, _, [2]} = emqtt:subscribe(C, Topic, qos2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - {ok, _} = emqtt:publish(C, Topic, <<"qos 2">>, 2), - ?assertEqual(3, length(recv_msgs(3))), - ok = emqtt:disconnect(C). +init_per_testcase(_TestCase, Config) -> + %% Meck CowboyReq + ok = meck:new(cowboy_req, [passthrough, no_history]), + ok = meck:expect(cowboy_req, peer, fun(_) -> {{127,0,0,1}, 3456} end), + ok = meck:expect(cowboy_req, sock, fun(_) -> {{127,0,0,1}, 8883} end), + ok = meck:expect(cowboy_req, cert, fun(_) -> undefined end), + ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> undefined end), + %% Meck Channel + ok = meck:new(emqx_channel, [passthrough, no_history]), + ok = meck:expect(emqx_channel, recvd, + fun(_Oct, Channel) -> + {ok, Channel} + end), + %% Meck Metrics + ok = meck:new(emqx_metrics, [passthrough, no_history]), + ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), + ok = meck:expect(emqx_metrics, inc_recv, fun(_) -> ok end), + ok = meck:expect(emqx_metrics, inc_sent, fun(_) -> ok end), + Config. -recv_msgs(Count) -> - recv_msgs(Count, []). +end_per_testcase(_TestCase, Config) -> + ok = meck:unload(cowboy_req), + ok = meck:unload(emqx_channel), + ok = meck:unload(emqx_metrics), + Config. + +%%-------------------------------------------------------------------- +%% Test Cases +%%-------------------------------------------------------------------- + +%%TODO:... +t_ws_conn_init(_) -> + with_ws_conn(fun(_WsConn) -> ok end). + +t_ws_conn_info(_) -> + with_ws_conn(fun(WsConn) -> + #{sockinfo := SockInfo} = emqx_ws_connection:info(WsConn), + #{socktype := ws, + peername := {{127,0,0,1}, 3456}, + sockname := {{127,0,0,1}, 8883}, + sockstate := idle} = SockInfo + end). + +t_ws_conn_stats(_) -> + with_ws_conn(fun(WsConn) -> + Stats = emqx_ws_connection:stats(WsConn), + lists:foreach(fun(Key) -> + 0 = proplists:get_value(Key, Stats) + end, ?STATS_KEYS) + end). + +t_websocket_init(_) -> + with_ws_conn(fun(WsConn) -> + #{sockinfo := SockInfo} = emqx_ws_connection:info(WsConn), + #{socktype := ws, + peername := {{127,0,0,1}, 3456}, + sockname := {{127,0,0,1}, 8883}, + sockstate := idle + } = SockInfo + end). + +t_websocket_handle_binary(_) -> + with_ws_conn(fun(WsConn) -> + ok = meck:expect(emqx_channel, recvd, fun(_Oct, Channel) -> {ok, Channel} end), + {ok, WsConn} = websocket_handle({binary, [<<>>]}, WsConn) + end). + +t_websocket_handle_ping_pong(_) -> + with_ws_conn(fun(WsConn) -> + {ok, WsConn} = websocket_handle(ping, WsConn), + {ok, WsConn} = websocket_handle(pong, WsConn), + {ok, WsConn} = websocket_handle({ping, <<>>}, WsConn), + {ok, WsConn} = websocket_handle({pong, <<>>}, WsConn) + end). + +t_websocket_handle_bad_frame(_) -> + with_ws_conn(fun(WsConn) -> + {stop, {shutdown, unexpected_ws_frame}, WsConn} + = websocket_handle({badframe, <<>>}, WsConn) + end). + +t_websocket_info_call(_) -> + with_ws_conn(fun(WsConn) -> + From = {make_ref(), self()}, + Call = {call, From, badreq}, + websocket_info(Call, WsConn) + end). + +t_websocket_info_cast(_) -> + with_ws_conn(fun(WsConn) -> + websocket_info({cast, msg}, WsConn) + end). + +t_websocket_info_incoming(_) -> + with_ws_conn(fun(WsConn) -> + Connect = ?CONNECT_PACKET( + #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, + proto_name = <<"MQTT">>, + clientid = <<>>, + clean_start = true, + keepalive = 60}), + {ok, WsConn1} = websocket_info({incoming, Connect}, WsConn), + Publish = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), + {ok, WsConn2} = websocket_info({incoming, Publish}, WsConn1) + end). + +t_websocket_info_deliver(_) -> + with_ws_conn(fun(WsConn) -> + Msg = emqx_message:make(<<"topic">>, <<"payload">>), + Deliver = {deliver, <<"#">>, Msg}, + {ok, WsConn1} = websocket_info(Deliver, WsConn) + end). + +t_websocket_info_timeout(_) -> + with_ws_conn(fun(WsConn) -> + websocket_info({timeout, make_ref(), keepalive}, WsConn), + websocket_info({timeout, make_ref(), emit_stats}, WsConn), + websocket_info({timeout, make_ref(), retry_delivery}, WsConn) + end). + +t_websocket_info_close(_) -> + with_ws_conn(fun(WsConn) -> + {stop, {shutdown, sock_error}, WsConn} = websocket_info({close, sock_error}, WsConn) + end). + +t_websocket_info_shutdown(_) -> + with_ws_conn(fun(WsConn) -> + {stop, {shutdown, reason}, WsConn} = websocket_info({shutdown, reason}, WsConn) + end). + +t_websocket_info_stop(_) -> + with_ws_conn(fun(WsConn) -> + {stop, normal, WsConn} = websocket_info({stop, normal}, WsConn) + end). + +t_websocket_close(_) -> + with_ws_conn(fun(WsConn) -> + {stop, sock_closed, WsConn} + = emqx_ws_connection:websocket_close(badframe, WsConn) + end). + +t_handle_call(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_handle_info(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_handle_timeout(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_parse_incoming(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_handle_incoming(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_handle_return(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +t_handle_outgoing(_) -> + with_ws_conn(fun(WsConn) -> ok end). + +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +with_ws_conn(TestFun) -> + with_ws_conn(TestFun, []). + +with_ws_conn(TestFun, Opts) -> + {ok, WsConn} = emqx_ws_connection:websocket_init( + [req, emqx_misc:merge_opts([{zone, external}], Opts)]), + TestFun(WsConn). -recv_msgs(0, Msgs) -> - Msgs; -recv_msgs(Count, Msgs) -> - receive - {publish, Msg} -> - recv_msgs(Count-1, [Msg|Msgs]) - after 100 -> - Msgs - end. From aecda09b9abe7a6cce73a4b8ced2f151b5749c90 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sat, 19 Oct 2019 20:18:34 +0800 Subject: [PATCH 04/40] Add more test cases --- src/emqx_session.erl | 48 +++++---- test/emqx_session_SUITE.erl | 189 ++++++++++++++++++++++++++++++++---- 2 files changed, 203 insertions(+), 34 deletions(-) diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 2b99d888b..67ac6e3ce 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -100,27 +100,25 @@ max_subscriptions :: non_neg_integer(), %% Upgrade QoS? upgrade_qos :: boolean(), - %% Client <- Broker: - %% Inflight QoS1, QoS2 messages sent to the client but unacked. + %% Client <- Broker: QoS1/2 messages sent to the client but unacked. inflight :: emqx_inflight:inflight(), - %% All QoS1, QoS2 messages published to when client is disconnected. - %% QoS 1 and QoS 2 messages pending transmission to the Client. + %% All QoS1/2 messages published to when client is disconnected, + %% or QoS1/2 messages pending transmission to the Client. %% - %% Optionally, QoS 0 messages pending transmission to the Client. + %% Optionally, QoS0 messages pending transmission to the Client. mqueue :: emqx_mqueue:mqueue(), %% Next packet id of the session next_pkt_id = 1 :: emqx_types:packet_id(), %% Retry interval for redelivering QoS1/2 messages retry_interval :: timeout(), - %% Client -> Broker: - %% Inflight QoS2 messages received from client and waiting for pubrel. + %% Client -> Broker: QoS2 messages received from client and waiting for pubrel. awaiting_rel :: map(), %% Max Packets Awaiting PUBREL max_awaiting_rel :: non_neg_integer(), %% Awaiting PUBREL Timeout await_rel_timeout :: timeout(), - %% Enqueue Count - enqueue_cnt :: non_neg_integer(), + %% Deliver Stats + deliver_stats :: emqx_types:stats(), %% Created at created_at :: pos_integer() }). @@ -131,7 +129,9 @@ -define(DEFAULT_BATCH_N, 1000). --define(ATTR_KEYS, [inflight_max, +-define(ATTR_KEYS, [inflight_cnt, + inflight_max, + mqueue_len, mqueue_max, retry_interval, awaiting_rel_max, @@ -168,7 +168,7 @@ ]). %%-------------------------------------------------------------------- -%% Init a session +%% Init a Session %%-------------------------------------------------------------------- %% @doc Init a session. @@ -184,10 +184,10 @@ init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> awaiting_rel = #{}, max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000), - enqueue_cnt = 0, created_at = erlang:system_time(second) }. +%% @private init mq init_mqueue(Zone) -> emqx_mqueue:init(#{max_len => get_env(Zone, max_mqueue_len, 1000), store_qos0 => get_env(Zone, mqueue_store_qos0, true), @@ -220,6 +220,8 @@ info(subscriptions_max, #session{max_subscriptions = MaxSubs}) -> info(upgrade_qos, #session{upgrade_qos = UpgradeQoS}) -> UpgradeQoS; info(inflight, #session{inflight = Inflight}) -> + Inflight; +info(inflight_cnt, #session{inflight = Inflight}) -> emqx_inflight:size(Inflight); info(inflight_max, #session{inflight = Inflight}) -> emqx_inflight:max_size(Inflight); @@ -234,13 +236,15 @@ info(mqueue_dropped, #session{mqueue = MQueue}) -> info(next_pkt_id, #session{next_pkt_id = PacketId}) -> PacketId; info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) -> - maps:size(AwaitingRel); + maps:values(AwaitingRel); +info(awaiting_rel_cnt, #session{awaiting_rel = AwaitingRel}) -> + AwaitingRel; info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) -> MaxAwaitingRel; info(await_rel_timeout, #session{await_rel_timeout = Timeout}) -> Timeout; -info(enqueue_cnt, #session{enqueue_cnt = Cnt}) -> - Cnt; +info(deliver_stats, #session{deliver_stats = Stats}) -> + Stats; info(created_at, #session{created_at = CreatedAt}) -> CreatedAt. @@ -506,7 +510,7 @@ enqueue(Delivers, Session = #session{subscriptions = Subs}) when is_list(Deliver || {deliver, Topic, Msg} <- Delivers], lists:foldl(fun enqueue/2, Session, Msgs); -enqueue(Msg, Session = #session{mqueue = Q, enqueue_cnt = Cnt}) +enqueue(Msg, Session = #session{mqueue = Q}) when is_record(Msg, message) -> {Dropped, NewQ} = emqx_mqueue:in(Msg, Q), if is_record(Dropped, message) -> @@ -514,7 +518,7 @@ enqueue(Msg, Session = #session{mqueue = Q, enqueue_cnt = Cnt}) [emqx_message:format(Dropped)]); true -> ok end, - Session#session{mqueue = NewQ, enqueue_cnt = Cnt+1}. + inc_deliver_stats(enqueue_cnt, Session#session{mqueue = NewQ}). %%-------------------------------------------------------------------- %% Awaiting ACK for QoS1/QoS2 Messages @@ -638,3 +642,13 @@ next_pkt_id(Session = #session{next_pkt_id = 16#FFFF}) -> next_pkt_id(Session = #session{next_pkt_id = Id}) -> Session#session{next_pkt_id = Id + 1}. +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +inc_deliver_stats(Key, Session) -> + inc_deliver_stats(Key, 1, Session). +inc_deliver_stats(Key, I, Session = #session{deliver_stats = Stats}) -> + NStats = maps:update_with(Key, fun(V) -> V+I end, I, Stats), + Session#session{deliver_stats = NStats}. + diff --git a/test/emqx_session_SUITE.erl b/test/emqx_session_SUITE.erl index dfa5da614..1fb41576f 100644 --- a/test/emqx_session_SUITE.erl +++ b/test/emqx_session_SUITE.erl @@ -23,22 +23,73 @@ -include_lib("proper/include/proper.hrl"). -include_lib("eunit/include/eunit.hrl"). +-import(emqx_session, [set_field/3]). + all() -> emqx_ct:all(?MODULE). -t_session_init(_) -> - error('TODO'). +%%-------------------------------------------------------------------- +%% CT callbacks +%%-------------------------------------------------------------------- + +init_per_testcase(_TestCase, Config) -> + %% Meck Broker + ok = meck:new(emqx_broker, [passthrough, no_history]), + ok = meck:new(emqx_hooks, [passthrough, no_history]), + ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end), + Config. + +end_per_testcase(_TestCase, Config) -> + ok = meck:unload(emqx_broker), + ok = meck:unload(emqx_hooks), + Config. %%-------------------------------------------------------------------- -%% Test cases for info/stats +%% Test cases for session init +%%-------------------------------------------------------------------- + +t_session_init(_) -> + Session = emqx_session:init(#{zone => external}, #{receive_maximum => 64}), + ?assertEqual(#{}, emqx_session:info(subscriptions, Session)), + ?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)), + ?assertEqual(0, emqx_session:info(subscriptions_max, Session)), + ?assertEqual(false, emqx_session:info(upgrade_qos, Session)), + ?assertEqual(0, emqx_session:info(inflight_cnt, Session)), + ?assertEqual(64, emqx_session:info(inflight_max, Session)), + ?assertEqual(1, emqx_session:info(next_pkt_id, Session)), + ?assertEqual(0, emqx_session:info(retry_interval, Session)), + ?assertEqual(0, emqx_session:info(awaiting_rel_cnt, Session)), + ?assertEqual(100, emqx_session:info(awaiting_rel_max, Session)), + ?assertEqual(3600000, emqx_session:info(awaiting_rel_timeout, Session)), + ?assert(is_integer(emqx_session:info(created_at, Session))). + +%%-------------------------------------------------------------------- +%% Test cases for session info/stats %%-------------------------------------------------------------------- t_session_info(_) -> - error('TODO'). + ?assertMatch(#{subscriptions := #{}, + subscriptions_max := 0, + upgrade_qos := false, + inflight := 0, + inflight_max := 64, + retry_interval := 0, + mqueue_len := 0, + mqueue_max := 1000, + mqueue_dropped := 0, + next_pkt_id := 1, + awaiting_rel := 0, + awaiting_rel_max := 0, + await_rel_timeout := 3600000 + }, emqx_session:info(session())). t_session_attrs(_) -> + Attrs = emqx_session:attrs(session()), + io:format("~p~n", [Attrs]), error('TODO'). t_session_stats(_) -> + Stats = emqx_session:stats(session()), + io:format("~p~n", [Stats]), error('TODO'). %%-------------------------------------------------------------------- @@ -46,36 +97,115 @@ t_session_stats(_) -> %%-------------------------------------------------------------------- t_subscribe(_) -> - error('TODO'). + ok = meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end), + ok = meck:expect(emqx_broker, set_subopts, fun(_, _) -> ok end), + {ok, Session} = emqx_session:subscribe( + clientinfo(), <<"#">>, subopts(), session()), + ?assertEqual(1, emqx_session:info(subscriptions_cnt, Session)). + +t_is_subscriptions_full_false(_) -> + Session = session(#{max_subscriptions => 0}), + ?assertNot(emqx_session:is_subscriptions_full(Session)). + +t_is_subscriptions_full_true(_) -> + Session = session(#{max_subscriptions => 1}), + ?assertNot(emqx_session:is_subscriptions_full(Session)), + Subs = #{<<"t1">> => subopts(), <<"t2">> => subopts()}, + NSession = set_field(subscriptions, Subs, Session), + ?assert(emqx_session:is_subscriptions_full(NSession)). t_unsubscribe(_) -> - error('TODO'). - -t_publish_qos0(_) -> - error('TODO'). - -t_publish_qos1(_) -> - error('TODO'). + ok = meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end), + Session = session(#{subscriptions => #{<<"#">> => subopts()}}), + {ok, NSession} = emqx_session:unsubscribe(clientinfo(), <<"#">>, Session), + Error = emqx_session:unsubscribe(clientinfo(), <<"#">>, NSession), + ?assertEqual({error, ?RC_NO_SUBSCRIPTION_EXISTED}, Error). t_publish_qos2(_) -> - error('TODO'). + ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), + Msg = emqx_message:make(test, ?QOS_2, <<"t">>, <<"payload">>), + {ok, [], Session} = emqx_session:publish(1, Msg, session()), + ?assertEqual(awaiting_rel_cnt, emqx_session:info(awaiting_rel_cnt, Session)). + +t_publish_qos1(_) -> + ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), + Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>), + {ok, [], Session} = emqx_session:publish(1, Msg, session()). + +t_publish_qos0(_) -> + ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), + Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>), + {ok, [], Session} = emqx_session:publish(0, Msg, session()). + +t_is_awaiting_full_false(_) -> + ?assertNot(emqx_session:is_awaiting_full(session(#{max_awaiting_rel => 0}))). + +t_is_awaiting_full_true(_) -> + Session = session(#{max_awaiting_rel => 1, + awaiting_rel => #{1 => 1} + }), + ?assert(emqx_session:is_awaiting_full(Session)). t_puback(_) -> - error('TODO'). + Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<>>), + Inflight = emqx_inflight:insert(1, {Msg, os:timestamp()}, emqx_inflight:new()), + Session = set_field(inflight, Inflight, session()), + {ok, Msg, NSession} = emqx_session:puback(1, Session), + ?assertEqual([], emqx_session:info(inflight, NSession)). + +t_puback_error_packet_id_in_use(_) -> + Inflight = emqx_inflight:insert(1, {pubrel, os:timestamp()}, emqx_inflight:new()), + Session = set_field(inflight, Inflight, session()), + {error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, Session). + +t_puback_error_packet_id_not_found(_) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:puback(1, session()). t_pubrec(_) -> - error('TODO'). + Msg = emqx_message:make(test, ?QOS_2, <<"t">>, <<>>), + Inflight = emqx_inflight:insert(2, {Msg, os:timestamp()}, emqx_inflight:new()), + Session = set_field(inflight, Inflight, session()), + {ok, Msg, NSession} = emqx_session:pubrec(2, Session), + ?assertMatch([{pubrel, _}], emqx_session:info(inflight, NSession)). + +t_pubrec_error_packet_id_in_use(_) -> + Inflight = emqx_inflight:insert(1, {pubrel, ts()}, emqx_inflight:new()), + Session = set_field(inflight, Inflight, session()), + {error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, session()). + +t_pubrec_error_packet_id_not_found(_) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubrec(1, session()). t_pubrel(_) -> - error('TODO'). + Session = set_field(awaiting_rel, #{1 => os:timestamp()}, session()), + {ok, NSession} = emqx_session:pubrel(1, Session), + ?assertEqual(#{}, emqx_session:info(awaiting_rel, NSession)). + +t_pubrel_id_not_found(_) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubrel(1, session()). t_pubcomp(_) -> - error('TODO'). + Inflight = emqx_inflight:insert(2, {pubrel, os:timestamp()}, emqx_inflight:new()), + Session = emqx_session:set_field(inflight, Inflight, session()), + {ok, NSession} = emqx_session:pubcomp(2, Session), + ?assertEqual([], emqx_session:info(inflight, NSession)). + +t_pubcomp_id_not_found(_) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubcomp(2, session()). %%-------------------------------------------------------------------- %% Test cases for deliver/retry %%-------------------------------------------------------------------- +t_dequeue(_) -> + {ok, Session} = emqx_session:dequeue(session()). + +t_bach_n(_) -> + error('TODO'). + +t_dequeue_with_msgs(_) -> + error('TODO'). + t_deliver(_) -> error('TODO'). @@ -101,3 +231,28 @@ t_redeliver(_) -> t_expire(_) -> error('TODO'). +%%-------------------------------------------------------------------- +%% Helper functions +%%-------------------------------------------------------------------- + +session() -> session(#{}). +session(InitFields) when is_map(InitFields) -> + maps:fold(fun(Field, Value, Session) -> + emqx_session:set_field(Field, Value, Session) + end, + emqx_session:init(#{zone => zone}, #{receive_maximum => 0}), + InitFields). + + +clientinfo() -> clientinfo(#{}). +clientinfo(Init) -> + maps:merge(#{clientid => <<"clientid">>, + username => <<"username">> + }, Init). + +subopts() -> subopts(#{}). +subopts(Init) -> + maps:merge(?DEFAULT_SUBOPTS, Init). + +ts() -> erlang:system_time(second). + From 26eab630b35e4973df34e4f62d73af87799a47ba Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Sun, 20 Oct 2019 19:20:38 +0800 Subject: [PATCH 05/40] Add test cases for emqx_channel module --- src/emqx_channel.erl | 9 +- test/emqx_channel_SUITE.erl | 645 +++++++++++++++++++++--------------- 2 files changed, 376 insertions(+), 278 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 012c35852..94916637d 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -148,6 +148,8 @@ info(will_msg, #channel{will_msg = WillMsg}) -> emqx_message:to_map(WillMsg); info(pub_stats, #channel{pub_stats = PubStats}) -> PubStats; +info(timers, #channel{timers = Timers}) -> + Timers; info(gc_state, #channel{gc_state = GcState}) -> maybe_apply(fun emqx_gc:info/1, GcState). @@ -635,12 +637,12 @@ handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn handle_out(puback, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)}; -handle_out(pubrel, {PacketId, ReasonCode}, Channel) -> - {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)}; - handle_out(pubrec, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)}; +handle_out(pubrel, {PacketId, ReasonCode}, Channel) -> + {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)}; + handle_out(pubcomp, {PacketId, ReasonCode}, Channel) -> {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)}; @@ -1120,6 +1122,7 @@ enrich_subopts(SubOpts, #channel{clientinfo = #{zone := Zone, is_bridge := IsBri NL = flag(emqx_zone:ignore_loop_deliver(Zone)), SubOpts#{rap => flag(IsBridge), nl => NL}. +%% TODO: Default caps should not be returned. enrich_caps(AckProps, #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}, clientinfo = #{zone := Zone}}) -> #{max_packet_size := MaxPktSize, diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index 8df85bea3..7d8c62433 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -19,11 +19,6 @@ -compile(export_all). -compile(nowarn_export_all). --import(emqx_channel, - [ handle_in/2 - , handle_out/2 - ]). - -include("emqx.hrl"). -include("emqx_mqtt.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -55,427 +50,527 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_testcase(_TestCase, Config) -> + ok = meck:new(emqx_broker, [passthrough, no_history]), + ok = meck:new(emqx_hooks, [passthrough, no_history]), + ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end), + ok = meck:expect(emqx_hooks, run_fold, fun(_Hook, _Args, Acc) -> Acc end), + ok = meck:new(emqx_session, [passthrough, no_history]), + ok = meck:new(emqx_metrics, [passthrough, no_history]), + ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), + ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), + Config. + +end_per_testcase(_TestCase, Config) -> + ok = meck:unload(emqx_metrics), + ok = meck:unload(emqx_session), + ok = meck:unload(emqx_broker), + ok = meck:unload(emqx_hooks), + Config. + %%-------------------------------------------------------------------- %% Test cases for channel info/stats/caps %%-------------------------------------------------------------------- -t_chan_info(_) -> error('TODO'). +t_chan_info(_) -> + #{conn_state := connected, + clientinfo := ClientInfo + } = emqx_channel:info(channel()), + ?assertEqual(clientinfo(), ClientInfo). -t_chan_attrs(_) -> error('TODO'). +t_chan_attrs(_) -> + #{conn_state := connected} = emqx_channel:attrs(channel()). -t_chan_stats(_) -> error('TODO'). +t_chan_stats(_) -> + [] = emqx_channel:stats(channel()). -t_chan_caps(_) -> error('TODO'). +t_chan_caps(_) -> + Caps = emqx_channel:caps(channel()). -t_chan_recvd(_) -> error('TODO'). +t_chan_recvd(_) -> + _Channel = emqx_channel:recvd(10, channel()). -t_chan_sent(_) -> error('TODO'). +t_chan_sent(_) -> + _Channel = emqx_channel:sent(10, channel()). %%-------------------------------------------------------------------- %% Test cases for channel init %%-------------------------------------------------------------------- -t_chan_init(_) -> error('TODO'). +%% TODO: +t_chan_init(_) -> + Channel = channel(). %%-------------------------------------------------------------------- %% Test cases for channel handle_in %%-------------------------------------------------------------------- -t_handle_in_connect_packet(_) -> - ConnPkt = #mqtt_packet_connect{ - proto_name = <<"MQTT">>, - proto_ver = ?MQTT_PROTO_V4, - is_bridge = false, - clean_start = true, - keepalive = 30, - properties = undefined, - clientid = <<"clientid">>, - username = <<"username">>, - password = <<"passwd">> - }, - with_chan(fun(Channel) -> - ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}), - ExpectedOutput = [{enter, connected},{outgoing, ConnAck}], - {ok, Output, Channel1} = handle_in(?CONNECT_PACKET(ConnPkt), Channel), - ?assertEqual(ExpectedOutput, Output), - #{clientid := ClientId, username := Username} = emqx_channel:info(clientinfo, Channel1), - ?assertEqual(<<"clientid">>, ClientId), - ?assertEqual(<<"username">>, Username) - end). +t_handle_in_connect_packet_sucess(_) -> + ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}), + {ok, {connack, ConnAck}, Channel} + = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), channel()), + ClientInfo = emqx_channel:info(clientinfo, Channel), + ?assertMatch(#{clientid := <<"clientid">>, + username := <<"username">> + }, ClientInfo), + ?assertEqual(connected, emqx_channel:info(conn_state, Channel)). t_handle_in_unexpected_connect_packet(_) -> - error('TODO'). + Channel = emqx_channel:set_field(conn_state, connected, channel()), + Result = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), Channel), + ?assertEqual({shutdown, protocol_error, Channel}, Result). t_handle_in_qos0_publish(_) -> - with_chan(fun(Channel) -> - Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), - {ok, Channel1} = handle_in(Publish, Channel), - ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + ok = meck:expect(emqx_broker, publish, fun(_) -> ok end), + Channel = channel(#{conn_state => connected}), + Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), + {ok, NChannel} = emqx_channel:handle_in(Publish, Channel), + ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_qos1_publish(_) -> - with_chan(fun(Channel) -> - Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), - {ok, ?PUBACK_PACKET(1, RC), _} = handle_in(Publish, Channel), - ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)) - end). + ok = meck:expect(emqx_broker, publish, fun(_) -> ok end), + Channel = channel(#{conn_state => connected}), + Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), + {ok, ?PUBACK_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), + ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), + ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_qos2_publish(_) -> - with_chan( - fun(Channel) -> - Publish1 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>), - {ok, ?PUBREC_PACKET(1, RC), Channel1} = handle_in(Publish1, Channel), - Publish2 = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 2, <<"payload">>), - {ok, ?PUBREC_PACKET(2, RC), Channel2} = handle_in(Publish2, Channel1), - ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), - #{awaiting_rel := AwaitingRel} = emqx_channel:info(session, Channel2), - ?assertEqual(2, AwaitingRel) - end). + ok = meck:expect(emqx_session, publish, fun(_, _Msg, Session) -> {ok, [], Session} end), + Channel = channel(#{conn_state => connected}), + Publish = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>), + {ok, ?PUBREC_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), + ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), + ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). -t_handle_in_puback(_) -> - with_chan( - fun(Channel) -> - {ok, Channel1} = handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel1)) - end). +t_handle_in_puback_ok(_) -> + Msg = emqx_message:make(<<"t">>, <<"payload">>), + ok = meck:expect(emqx_session, puback, + fun(PacketId, Session) -> {ok, Msg, Session} end), + Channel = channel(#{conn_state => connected}), + {ok, NChannel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, NChannel)). -t_handle_in_pubrec(_) -> - with_chan( - fun(Channel) -> - {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} - = handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). +t_handle_in_puback_id_in_use(_) -> + ok = meck:expect(emqx_session, puback, + fun(_, _Session) -> + {error, ?RC_PACKET_IDENTIFIER_IN_USE} + end), + Channel = channel(#{conn_state => connected}), + PubAck = ?PUBACK_PACKET(1, ?RC_SUCCESS), + {ok, Channel} = emqx_channel:handle_in(PubAck, Channel). -t_handle_in_pubrel(_) -> - with_chan( - fun(Channel) -> - {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel1} - = handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). +t_handle_in_puback_id_not_found(_) -> + ok = meck:expect(emqx_session, puback, + fun(_, _Session) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end), + Channel = channel(#{conn_state => connected}), + PubAck = ?PUBACK_PACKET(1, ?RC_SUCCESS), + {ok, Channel} = emqx_channel:handle_in(PubAck, Channel). -t_handle_in_pubcomp(_) -> - with_chan( - fun(Channel) -> - {ok, Channel1} = handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)) - end). +t_handle_in_pubrec_ok(_) -> + Msg = emqx_message:make(test,?QOS_2, <<"t">>, <<"payload">>), + ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {ok, Msg, Session} end), + Channel = channel(#{conn_state => connected}), + {ok, ?PUBREL_PACKET(1, ?RC_SUCCESS), Channel1} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + emqx_channel:info(pub_stats, Channel1)). + +t_handle_in_pubrec_id_in_use(_) -> + ok = meck:expect(emqx_session, pubrec, + fun(_, Session) -> + {error, ?RC_PACKET_IDENTIFIER_IN_USE} + end), + Channel = channel(#{conn_state => connected}), + {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), Channel} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel). + +t_handle_in_pubrec_id_not_found(_) -> + ok = meck:expect(emqx_session, pubrec, + fun(_, Session) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end), + Channel = channel(#{conn_state => connected}), + {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel). + +t_handle_in_pubrel_ok(_) -> + ok = meck:expect(emqx_session, pubrel, fun(_, Session) -> {ok, Session} end), + Channel = channel(#{conn_state => connected}), + {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel1} + = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, + emqx_channel:info(pub_stats, Channel1)). + +t_handle_in_pubrel_not_found_error(_) -> + ok = meck:expect(emqx_session, pubrel, + fun(_PacketId, _Session) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end), + Channel = channel(#{conn_state => connected}), + {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} + = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel). + +t_handle_in_pubcomp_ok(_) -> + ok = meck:expect(emqx_session, pubcomp, fun(_, Session) -> {ok, Session} end), + Channel = channel(#{conn_state => connected}), + {ok, Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)). + +t_handle_in_pubcomp_not_found_error(_) -> + ok = meck:expect(emqx_session, pubcomp, + fun(_PacketId, _Session) -> + {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} + end), + Channel = channel(#{conn_state => connected}), + {ok, Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), + ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)). t_handle_in_subscribe(_) -> - with_chan( - fun(Channel) -> - TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], - {ok, ?SUBACK_PACKET(10, [?QOS_0]), Channel1} - = handle_in(?SUBSCRIBE_PACKET(10, #{}, TopicFilters), Channel), - #{subscriptions := Subscriptions} - = emqx_channel:info(session, Channel1), - ?assertEqual(maps:from_list(TopicFilters), Subscriptions) - end). + ok = meck:expect(emqx_session, subscribe, + fun(_, _, _, Session) -> + {ok, Session} + end), + Channel = channel(#{conn_state => connected}), + TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], + Subscribe = ?SUBSCRIBE_PACKET(1, #{}, TopicFilters), + {ok, ?SUBACK_PACKET(1, [?QOS_0]), _} = emqx_channel:handle_in(Subscribe, Channel). t_handle_in_unsubscribe(_) -> - with_chan( - fun(Channel) -> - {ok, ?UNSUBACK_PACKET(11), Channel} - = handle_in(?UNSUBSCRIBE_PACKET(11, #{}, [<<"+">>]), Channel) - end). + ok = meck:expect(emqx_session, unsubscribe, + fun(_, _, Session) -> + {ok, Session} + end), + Channel = channel(#{conn_state => connected}), + UnsubPkt = ?UNSUBSCRIBE_PACKET(1, #{}, [<<"+">>]), + {ok, ?UNSUBACK_PACKET(1), _} = emqx_channel:handle_in(UnsubPkt, Channel). t_handle_in_pingreq(_) -> - with_chan(fun(Channel) -> - {ok, ?PACKET(?PINGRESP), Channel} = handle_in(?PACKET(?PINGREQ), Channel) - end). + {ok, ?PACKET(?PINGRESP), _Channel} + = emqx_channel:handle_in(?PACKET(?PINGREQ), channel()). t_handle_in_disconnect(_) -> - with_chan(fun(Channel) -> - {stop, {shutdown, normal}, Channel1} = handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel), - ?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)) - end). + Channel = channel(#{conn_state => connected}), + {shutdown, normal, Channel1} = emqx_channel:handle_in(?DISCONNECT_PACKET(?RC_SUCCESS), Channel), + ?assertEqual(undefined, emqx_channel:info(will_msg, Channel1)). t_handle_in_auth(_) -> - with_chan(fun(Channel) -> - Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR), - {stop, {shutdown, implementation_specific_error}, Packet, Channel} = handle_in(?AUTH_PACKET(), Channel) - end). + Channel = channel(#{conn_state => connected}), + Packet = ?DISCONNECT_PACKET(?RC_IMPLEMENTATION_SPECIFIC_ERROR), + {shutdown, implementation_specific_error, Packet, Channel} + = emqx_channel:handle_in(?AUTH_PACKET(), Channel). t_handle_in_frame_error(_) -> - with_chan(fun(Channel) -> error('TODO') end). + IdleChannel = channel(#{conn_state => idle}), + {shutdown, frame_too_large, _} + = emqx_channel:handle_in({frame_error, frame_too_large}, IdleChannel), + ConnectingChan = channel(#{conn_state => connecting}), + {shutdown, frame_too_large, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), _} + = emqx_channel:handle_in({frame_error, frame_too_large}, ConnectingChan), + ConnectedChan = channel(#{conn_state => connected}), + {shutdown, frame_too_large, ?DISCONNECT_PACKET(?RC_MALFORMED_PACKET), _} + = emqx_channel:handle_in({frame_error, frame_too_large}, ConnectedChan), + DisconnectedChan = channel(#{conn_state => disconnected}), + {ok, DisconnectedChan} + = emqx_channel:handle_in({frame_error, frame_too_large}, DisconnectedChan). t_handle_in_expected_packet(_) -> - with_chan(fun(Channel) -> error('TODO') end). + {ok, _Chan} = emqx_channel:handle_in(packet, channel()). t_process_connect(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = meck:expect(emqx_cm, open_session, + fun(true, _ClientInfo, _ConnInfo) -> + {ok, #{session => session(), present => false}} + end), + ConnPkt = connpkt(), + {ok, ?CONNACK_PACKET(?RC_SUCCESS), ConnPkt, _} + = emqx_channel:process_connect(ConnPkt, channel()). t_handle_publish(_) -> - with_chan(fun(Channel) -> error('TODO') end). + Publish = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _Channel} + = emqx_channel:handle_publish(Publish, channel()). -t_process_publish(_) -> - with_chan(fun(Channel) -> error('TODO') end). +t_process_publish_qos1(_) -> + Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>), + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _Channel} + = emqx_channel:process_publish(1, Msg, channel()). t_process_subscribe(_) -> - with_chan(fun(Channel) -> error('TODO') end). + TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], + {[?RC_SUCCESS], _Channel} = emqx_channel:process_subscribe(TopicFilters, channel()). t_process_unsubscribe(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = meck:expect(emqx_session, unsubscribe, fun(_, _, Session) -> {ok, Session} end), + TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], + {[?RC_SUCCESS], _Channel} = emqx_channel:process_unsubscribe(TopicFilters, channel()). %%-------------------------------------------------------------------- %% Test cases for handle_out %%-------------------------------------------------------------------- t_handle_out_delivers(_) -> - with_chan(fun(Channel) -> - TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS#{qos => ?QOS_2}}], - {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel1} - = handle_in(?SUBSCRIBE_PACKET(1, #{}, TopicFilters), Channel), - Msg0 = emqx_message:make(<<"clientx">>, ?QOS_0, <<"t0">>, <<"qos0">>), - Msg1 = emqx_message:make(<<"clientx">>, ?QOS_1, <<"t1">>, <<"qos1">>), - Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}], - {ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, Channel1), - ?assertEqual([?QOS_0, ?QOS_1], [emqx_packet:qos(Pkt)|| Pkt <- Packets]) - end). + ok = emqx_meck:expect(emqx_session, deliver, + fun(Delivers, Session) -> + Msgs = [Msg || {deliver, _, Msg} <- Delivers], + [{publish, PacketId, Msg} || {PacketId, Msg} <- lists:zip(lists:seq(1, length(Msgs)), Msgs)] + end), + Msg0 = emqx_message:make(test, ?QOS_1, <<"t1">>, <<"qos1">>), + Msg1 = emqx_message:make(test, ?QOS_2, <<"t2">>, <<"qos2">>), + Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}], + {ok, {outgoing, Packets}, _Ch} = emqx_channel:handle_out(Delivers, channel()), + ?assertEqual([?QOS_1, ?QOS_2], [emqx_packet:qos(Pkt)|| Pkt <- Packets]). -t_handle_out_connack_sucess(_) -> - ConnPkt = #mqtt_packet_connect{ - proto_name = <<"MQTT">>, - proto_ver = ?MQTT_PROTO_V4, - clean_start = true, - properties = #{}, - clientid = <<"clientid">> - }, - with_chan(fun(Channel) -> - {ok, [{enter, connected},{outgoing, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan} - = handle_out({connack, ?RC_SUCCESS, 0, ConnPkt}, Channel), - {stop, {shutdown, not_authorized}, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _} - = handle_out({connack, ?RC_NOT_AUTHORIZED, ConnPkt}, Channel) - end). - -t_handle_out_connack_failure(_) -> - with_chan(fun(Channel) -> error('TODO') end). +t_handle_out_publishes(_) -> + Channel = channel(#{conn_state => connected}), + Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)}, + Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, + {ok, {outgoing, Packets}, NChannel} + = emqx_channel:handle_out({publish, [Pub0, Pub1]}, Channel), + ?assertEqual(2, length(Packets)), + ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_publish(_) -> - with_chan(fun(Channel) -> - Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)}, - Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, - {ok, ?PUBLISH_PACKET(?QOS_0), Channel} = handle_out(Pub0, Channel), - {ok, ?PUBLISH_PACKET(?QOS_1), Channel} = handle_out(Pub1, Channel), - {ok, {outgoing, Packets}, Channel1} = handle_out({publish, [Pub0, Pub1]}, Channel), - ?assertEqual(2, length(Packets)), - ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, Channel1)) - end). + Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t">>, <<"payload">>), + {ok, ?PUBLISH_PACKET(?QOS_1, <<"t">>, <<"payload">>), _Channel} + = emqx_channel:handle_out({publish, 1, Msg}, channel()). + +t_handle_out_publish_nl(_) -> + ClientInfo = clientinfo(#{clientid => <<"clientid">>}), + Channel = channel(#{clientinfo => ClientInfo}), + Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t1">>, <<"qos1">>), + Publish = {publish, 1, emqx_message:set_flag(nl, Msg)}, + {ok, Channel} = emqx_channel:handle_out(Publish, Channel). + +t_handle_out_connack_sucess(_) -> + Channel = channel(#{conn_state => connected}), + {ok, {connack, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}, _Chan} + = emqx_channel:handle_out({connack, ?RC_SUCCESS, 0, connpkt()}, Channel). + +t_handle_out_connack_failure(_) -> + {shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _Chan} + = emqx_channel:handle_out({connack, ?RC_NOT_AUTHORIZED, connpkt()}, channel()). t_handle_out_puback(_) -> - with_chan(fun(Channel) -> - {ok, Channel} = handle_out({puberr, ?RC_NOT_AUTHORIZED}, Channel), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), Channel1} - = handle_out({puback, 1, ?RC_SUCCESS}, Channel), - ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + Channel = channel(#{conn_state => connected}), + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), NChannel} + = emqx_channel:handle_out(puback, {1, ?RC_SUCCESS}, Channel), + ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_pubrec(_) -> - with_chan(fun(Channel) -> - {ok, ?PUBREC_PACKET(4, ?RC_SUCCESS), Channel1} - = handle_out({pubrec, 4, ?RC_SUCCESS}, Channel), - ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + Channel = channel(#{conn_state => connected}), + {ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), NChannel} + = emqx_channel:handle_out({pubrec, 1, ?RC_SUCCESS}, Channel), + ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_pubrel(_) -> - with_chan(fun(Channel) -> - {ok, ?PUBREL_PACKET(2), Channel1} - = handle_out({pubrel, 2, ?RC_SUCCESS}, Channel), - {ok, ?PUBREL_PACKET(3, ?RC_SUCCESS), Channel2} - = handle_out({pubrel, 3, ?RC_SUCCESS}, Channel1), - ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)) - end). + Channel = channel(#{conn_state => connected}), + {ok, ?PUBREL_PACKET(1), Channel1} + = emqx_channel:handle_out({pubrel, 1, ?RC_SUCCESS}, Channel), + {ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), Channel2} + = emqx_channel:handle_out({pubrel, 2, ?RC_SUCCESS}, Channel1), + ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)). t_handle_out_pubcomp(_) -> - with_chan(fun(Channel) -> - {ok, ?PUBCOMP_PACKET(5, ?RC_SUCCESS), Channel1} - = handle_out({pubcomp, 5, ?RC_SUCCESS}, Channel), - ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel1)) - end). + {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel} + = emqx_channel:handle_out({pubcomp, 2, ?RC_SUCCESS}, channel()), + ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_out_suback(_) -> - with_chan(fun(Channel) -> - {ok, ?SUBACK_PACKET(1, [?QOS_2]), Channel} - = handle_out({suback, 1, [?QOS_2]}, Channel) - end). + {ok, ?SUBACK_PACKET(1, [?QOS_2]), _Channel} + = emqx_channel:handle_out({suback, 1, [?QOS_2]}, channel()). t_handle_out_unsuback(_) -> - with_chan(fun(Channel) -> - {ok, ?UNSUBACK_PACKET(1), Channel} - = handle_out({unsuback, 1, [?RC_SUCCESS]}, Channel) - end). + {ok, ?UNSUBACK_PACKET(1, [?RC_SUCCESS]), _Channel} + = emqx_channel:handle_out({unsuback, 1, [?RC_SUCCESS]}, channel()). t_handle_out_disconnect(_) -> - with_chan( - fun(Channel) -> - handle_out({disconnect, ?RC_SUCCESS}, Channel) - end). + {shutdown, normal, ?DISCONNECT_PACKET(?RC_SUCCESS), _Chan} + = emqx_channel:handle_out({disconnect, ?RC_SUCCESS}, channel()). t_handle_out_unexpected(_) -> - with_chan(fun(Channel) -> - handle_out({disconnect, ?RC_SUCCESS}, Channel) - end). + {ok, _Channel} = emqx_channel:handle_out(unexpected, <<"data">>, channel()). %%-------------------------------------------------------------------- %% Test cases for handle_call %%-------------------------------------------------------------------- t_handle_call_kick(_) -> - error('TODO'). + {shutdown, kicked, ok, _Chan} = emqx_channel:handle_call(kick, channel()). t_handle_call_discard(_) -> - error('TODO'). + Packet = ?DISCONNECT_PACKET(?RC_SESSION_TAKEN_OVER), + {shutdown, discarded, ok, Packet, _Channel} + = emqx_channel:handle_call(discard, channel()). -t_handle_call_takeover(_) -> - error('TODO'). +t_handle_call_takeover_begin(_) -> + {reply, undefined, _Channel} + = emqx_channel:handle_call({takeover, 'begin'}, channel()). + +t_handle_call_takeover_end(_) -> + ok = meck:expect(emqx_session, takeover, fun(_) -> ok end), + {shutdown, takeovered, [], _Channel} + = emqx_channel:handle_call({takeover, 'end'}, channel()). t_handle_call_unexpected(_) -> - error('TODO'). + {reply, ignored, _Channel} = emqx_channel:handle_call(unexpected_req, channel()). %%-------------------------------------------------------------------- %% Test cases for handle_info %%-------------------------------------------------------------------- t_handle_info_subscribe(_) -> - error('TODO'). + ok = meck:expect(emqx_session, subscribe, fun(_, _, _, Session) -> {ok, Session} end), + {ok, _Chan} = emqx_channel:handle_info({subscribe, topic_filters()}, channel()). t_handle_info_unsubscribe(_) -> - error('TODO'). + ok = meck:expect(emqx_session, unsubscribe, fun(_, _, Session) -> {ok, Session} end), + {ok, _Chan} = emqx_channel:handle_info({unsubscribe, topic_filters()}, channel()). t_handle_info_sock_closed(_) -> - error('TODO'). + {ok, _Chan} = emqx_channel:handle_out({sock_closed, reason}, channel(#{conn_state => disconnected})). %%-------------------------------------------------------------------- %% Test cases for handle_timeout %%-------------------------------------------------------------------- t_handle_timeout_emit_stats(_) -> - with_chan(fun(Channel) -> 'TODO' end). + {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), {emit_stats, []}, channel()). t_handle_timeout_keepalive(_) -> - with_chan(fun(Channel) -> 'TODO' end). + {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), {keepalive, 10}, channel()). t_handle_timeout_retry_delivery(_) -> - with_chan(fun(Channel) -> 'TODO' end). + {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), retry_delivery, channel()). t_handle_timeout_expire_awaiting_rel(_) -> - with_chan(fun(Channel) -> 'TODO' end). + {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), expire_awaiting_rel, channel()). t_handle_timeout_expire_session(_) -> - with_chan(fun(Channel) -> 'TODO' end). + {shutdown, expired, _Chan} = emqx_channel:handle_timeout(make_ref(), expire_awaiting_rel, channel()). t_handle_timeout_will_message(_) -> - with_chan(fun(Channel) -> 'TODO' end). - -%%-------------------------------------------------------------------- -%% Test cases for ensure_timer -%%-------------------------------------------------------------------- - -t_ensure_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_reset_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_alive_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_retry_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_await_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_expire_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_will_timer(_) -> - with_chan(fun(Channel) -> error('TODO') end). + {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), will_message, channel()). %%-------------------------------------------------------------------- %% Test cases for internal functions %%-------------------------------------------------------------------- t_enrich_conninfo(_) -> - with_chan(fun(Channel) -> error('TODO') end). + {ok, _Chan} = emqx_channel:enrich_conninfo(connpkt(), channel()). t_enrich_client(_) -> - with_chan(fun(Channel) -> error('TODO') end). + {ok, _ConnPkt, _Chan} = emqx_channel:enrich_client(connpkt(), channel()). t_check_banned(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = emqx_channel:check_banned(connpkt(), channel()). t_check_flapping(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = emqx_channel:check_flapping(connpkt(), channel()). t_auth_connect(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = meck:expect(emqx_access_control, authenticate, fun(_) -> {ok, #{}} end), + {ok, _Chan} = emqx_channel:auth_connect(connpkt(), channel()). t_process_alias(_) -> - with_chan(fun(Channel) -> error('TODO') end). + Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}}, + Channel = emqx_channel:set_field(topic_aliases, #{1 => <<"t">>}, channel()), + {ok, #mqtt_packet{variable = #mqtt_packet_publish{topic_name = <<"t">>}}, _Chan} + = emqx_channel:process_alias(#mqtt_packet{variable = Publish}, Channel). t_check_pub_acl(_) -> - with_chan(fun(Channel) -> error('TODO') end). + Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), + ok = emqx_channel:check_pub_acl(Publish, channel()). t_check_pub_alias(_) -> - with_chan(fun(Channel) -> error('TODO') end). + Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}}, + ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, channel()). t_check_subscribe(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_check_sub_acl(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_check_sub_caps(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_enrich_subid(_) -> - with_chan(fun(Channel) -> error('TODO') end). - -t_enrich_subopts(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = emqx_channel:check_subscribe(<<"t">>, ?DEFAULT_SUBOPTS, channel()). t_enrich_caps(_) -> - with_chan(fun(Channel) -> error('TODO') end). + ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]), + ok = meck:expect(emqx_mqtt_caps, get_caps, + fun(_Zone) -> + #{max_packet_size => 1024, + max_qos_allowed => ?QOS_2, + retain_available => true, + max_topic_alias => 10, + shared_subscription => true, + wildcard_subscription => true + } + end), + AckProps = emqx_channel:enrich_caps(#{}, channel()), + ?assertMatch(#{'Retain-Available' := 1, + 'Maximum-Packet-Size' := 1024, + 'Topic-Alias-Maximum' := 10, + 'Wildcard-Subscription-Available' := 1, + 'Subscription-Identifier-Available' := 1, + 'Shared-Subscription-Available' := 1, + 'Maximum-QoS' := ?QOS_2 + }, AckProps), + ok = meck:unload(emqx_mqtt_caps). %%-------------------------------------------------------------------- %% Test cases for terminate %%-------------------------------------------------------------------- t_terminate(_) -> - with_chan( - fun(Channel) -> - 'TODO' - end). + ok = emqx_channel:terminate(normal, channel()), + ok = emqx_channel:terminate(sock_error, channel(#{conn_state => connected})), + ok = emqx_channel:terminate({shutdown, kicked}, channel(#{conn_state => connected})). %%-------------------------------------------------------------------- %% Helper functions %%-------------------------------------------------------------------- -with_connected_channel(TestFun) -> - with_chan( - fun(Channel) -> - TestFun(emqx_channel:set_field(conn_state, connected, Channel)) - end). +channel() -> channel(#{}). +channel(InitFields) -> + maps:fold(fun(Field, Value, Channel) -> + emqx_channel:set_field(Field, Value, Channel) + end, default_channel(), InitFields). -with_chan(TestFun) -> - with_chan(#{}, TestFun). +default_channel() -> + Channel = emqx_channel:init(?DEFAULT_CONNINFO, [{zone, zone}]), + Channel1 = emqx_channel:set_field(conn_state, connected, Channel), + emqx_channel:set_field(clientinfo, clientinfo(), Channel1). -with_chan(ConnInfo, TestFun) -> - ConnInfo1 = maps:merge(?DEFAULT_CONNINFO, ConnInfo), - ClientInfo = #{zone => <<"external">>, - protocol => mqtt, - peerhost => {127,0,0,1}, - clientid => <<"clientid">>, - username => <<"username">>, - peercert => undefined, - is_bridge => false, - is_superuser => false, - mountpoint => undefined - }, - Channel = emqx_channel:init(ConnInfo1, [{zone, testing}]), - Session = emqx_session:init(ClientInfo, ConnInfo1), - Channel1 = emqx_channel:set_field(clientinfo, ClientInfo, Channel), - TestFun(emqx_channel:set_field(session, Session, Channel1)). +clientinfo() -> clientinfo(#{}). +clientinfo(InitProps) -> + maps:merge(#{zone => zone, + protocol => mqtt, + peerhost => {127,0,0,1}, + clientid => <<"clientid">>, + username => <<"username">>, + is_superuser => false, + peercert => undefined, + mountpoint => undefined + }, InitProps). + +topic_filters() -> + [{<<"+">>, ?DEFAULT_SUBOPTS}, {<<"#">>, ?DEFAULT_SUBOPTS}]. + +connpkt() -> + #mqtt_packet_connect{ + proto_name = <<"MQTT">>, + proto_ver = ?MQTT_PROTO_V4, + is_bridge = false, + clean_start = true, + keepalive = 30, + properties = undefined, + clientid = <<"clientid">>, + username = <<"username">>, + password = <<"passwd">> + }. + +session() -> session(#{}). +session(InitFields) when is_map(InitFields) -> + maps:fold(fun(Field, Value, Session) -> + emqx_session:set_field(Field, Value, Session) + end, + emqx_session:init(#{zone => zone}, #{receive_maximum => 0}), + InitFields). From 7117dde87989042223d0bfd9aad40a28d9732b18 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 21 Oct 2019 17:14:50 +0800 Subject: [PATCH 06/40] Add more test cases for connection, channel and session modules --- src/emqx_channel.erl | 12 ++- src/emqx_connection.erl | 17 +++- src/emqx_session.erl | 31 +++--- src/emqx_ws_connection.erl | 13 ++- test/emqx_channel_SUITE.erl | 161 +++++++++++++++++++----------- test/emqx_session_SUITE.erl | 66 ++++++------ test/emqx_ws_connection_SUITE.erl | 43 +++++--- 7 files changed, 217 insertions(+), 126 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 94916637d..99a595929 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -615,13 +615,13 @@ handle_out(connack, {?RC_SUCCESS, SP, ConnPkt}, AckPacket = ?CONNACK_PACKET(?RC_SUCCESS, SP, AckProps), case maybe_resume_session(Channel2) of ignore -> - {ok, [{enter, connected}, {outgoing, AckPacket}], Channel2}; + {ok, [{connack, AckPacket}], Channel2}; {ok, Publishes, NSession} -> Channel3 = Channel2#channel{session = NSession, resuming = false, pendings = []}, {ok, {outgoing, Packets}, _} = handle_out({publish, Publishes}, Channel3), - {ok, [{enter, connected}, {outgoing, [AckPacket|Packets]}], Channel3} + {ok, [{connack, AckPacket}, {outgoing, Packets}], Channel3} end; handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnInfo, @@ -880,7 +880,7 @@ interval(alive_timer, #channel{keepalive = KeepAlive}) -> interval(retry_timer, #channel{session = Session}) -> emqx_session:info(retry_interval, Session); interval(await_timer, #channel{session = Session}) -> - emqx_session:info(await_rel_timeout, Session); + emqx_session:info(awaiting_rel_timeout, Session); interval(expire_timer, #channel{conninfo = ConnInfo}) -> timer:seconds(maps:get(expiry_interval, ConnInfo)); interval(will_timer, #channel{will_msg = WillMsg}) -> @@ -1068,13 +1068,13 @@ check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}}, end. %% Check Pub Alias +%% TODO: Fixme later check_pub_alias(#mqtt_packet{ variable = #mqtt_packet_publish{ properties = #{'Topic-Alias' := AliasId} } }, #channel{alias_maximum = Limits}) -> - %% TODO: Move to Protocol case (Limits == undefined) orelse (Max = maps:get(inbound, Limits, 0)) == 0 orelse (AliasId > Max) of @@ -1219,10 +1219,14 @@ reply(Reply, Channel) -> {reply, Reply, Channel}. -compile({inline, [shutdown/2]}). +shutdown(success, Channel) -> + shutdown(normal, Channel); shutdown(Reason, Channel) -> {shutdown, Reason, Channel}. -compile({inline, [shutdown/3]}). +shutdown(success, Reply, Channel) -> + shutdown(normal, Reply, Channel); shutdown(Reason, Reply, Channel) -> {shutdown, Reason, Reply, Channel}. diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index 1da494dfa..b557d42b7 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -516,9 +516,20 @@ send(IoData, State = #state{transport = Transport, %%-------------------------------------------------------------------- %% Handle Info -handle_info({enter, _}, State = #state{active_n = ActiveN, - sockstate = SockSt, - channel = Channel}) -> +handle_info({connack, ConnAck}, State = #state{active_n = ActiveN, + sockstate = SockSt, + channel = Channel}) -> + NState = handle_outgoing(ConnAck, State), + ChanAttrs = emqx_channel:attrs(Channel), + SockAttrs = #{active_n => ActiveN, + sockstate => SockSt + }, + Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), + handle_info({register, Attrs, stats(State)}, NState); + +handle_info({enter, disconnected}, State = #state{active_n = ActiveN, + sockstate = SockSt, + channel = Channel}) -> ChanAttrs = emqx_channel:attrs(Channel), SockAttrs = #{active_n => ActiveN, sockstate => SockSt diff --git a/src/emqx_session.erl b/src/emqx_session.erl index 67ac6e3ce..6879dcc06 100644 --- a/src/emqx_session.erl +++ b/src/emqx_session.erl @@ -116,7 +116,7 @@ %% Max Packets Awaiting PUBREL max_awaiting_rel :: non_neg_integer(), %% Awaiting PUBREL Timeout - await_rel_timeout :: timeout(), + awaiting_rel_timeout :: timeout(), %% Deliver Stats deliver_stats :: emqx_types:stats(), %% Created at @@ -135,7 +135,7 @@ mqueue_max, retry_interval, awaiting_rel_max, - await_rel_timeout, + awaiting_rel_timeout, created_at ]). @@ -151,7 +151,7 @@ next_pkt_id, awaiting_rel, awaiting_rel_max, - await_rel_timeout, + awaiting_rel_timeout, created_at ]). @@ -183,7 +183,7 @@ init(#{zone := Zone}, #{receive_maximum := MaxInflight}) -> retry_interval = get_env(Zone, retry_interval, 0), awaiting_rel = #{}, max_awaiting_rel = get_env(Zone, max_awaiting_rel, 100), - await_rel_timeout = get_env(Zone, await_rel_timeout, 3600*1000), + awaiting_rel_timeout = get_env(Zone, awaiting_rel_timeout, 3600*1000), created_at = erlang:system_time(second) }. @@ -236,13 +236,17 @@ info(mqueue_dropped, #session{mqueue = MQueue}) -> info(next_pkt_id, #session{next_pkt_id = PacketId}) -> PacketId; info(awaiting_rel, #session{awaiting_rel = AwaitingRel}) -> - maps:values(AwaitingRel); -info(awaiting_rel_cnt, #session{awaiting_rel = AwaitingRel}) -> AwaitingRel; +info(awaiting_rel_cnt, #session{awaiting_rel = AwaitingRel}) -> + maps:size(AwaitingRel); info(awaiting_rel_max, #session{max_awaiting_rel = MaxAwaitingRel}) -> MaxAwaitingRel; -info(await_rel_timeout, #session{await_rel_timeout = Timeout}) -> +info(awaiting_rel_timeout, #session{awaiting_rel_timeout = Timeout}) -> Timeout; +info(enqueue_cnt, #session{deliver_stats = undefined}) -> + 0; +info(enqueue_cnt, #session{deliver_stats = Stats}) -> + maps:get(enqueue_cnt, Stats, 0); info(deliver_stats, #session{deliver_stats = Stats}) -> Stats; info(created_at, #session{created_at = CreatedAt}) -> @@ -338,8 +342,7 @@ unsubscribe(ClientInfo, TopicFilter, Session = #session{subscriptions = Subs}) - %%-------------------------------------------------------------------- -spec(publish(emqx_types:packet_id(), emqx_types:message(), session()) - -> {ok, emqx_types:publish_result()} | - {ok, emqx_types:publish_result(), session()} | + -> {ok, emqx_types:publish_result(), session()} | {error, emqx_types:reason_code()}). publish(PacketId, Msg = #message{qos = ?QOS_2}, Session) -> case is_awaiting_full(Session) of @@ -350,8 +353,8 @@ publish(PacketId, Msg = #message{qos = ?QOS_2}, Session) -> end; %% Publish QoS0/1 directly -publish(_PacketId, Msg, _Session) -> - {ok, emqx_broker:publish(Msg)}. +publish(_PacketId, Msg, Session) -> + {ok, emqx_broker:publish(Msg), Session}. is_awaiting_full(#session{max_awaiting_rel = 0}) -> false; @@ -621,11 +624,11 @@ expire_awaiting_rel([], _Now, Session) -> expire_awaiting_rel([{PacketId, Ts} | More], Now, Session = #session{awaiting_rel = AwaitingRel, - await_rel_timeout = Timeout}) -> + awaiting_rel_timeout = Timeout}) -> case (timer:now_diff(Now, Ts) div 1000) of Age when Age >= Timeout -> ok = emqx_metrics:inc('messages.qos2.expired'), - ?LOG(warning, "Dropped qos2 packet ~s for await_rel_timeout", [PacketId]), + ?LOG(warning, "Dropped qos2 packet ~s for awaiting_rel_timeout", [PacketId]), Session1 = Session#session{awaiting_rel = maps:remove(PacketId, AwaitingRel)}, expire_awaiting_rel(More, Now, Session1); Age -> @@ -648,6 +651,8 @@ next_pkt_id(Session = #session{next_pkt_id = Id}) -> inc_deliver_stats(Key, Session) -> inc_deliver_stats(Key, 1, Session). +inc_deliver_stats(Key, I, Session = #session{deliver_stats = undefined}) -> + Session#session{deliver_stats = #{Key => I}}; inc_deliver_stats(Key, I, Session = #session{deliver_stats = Stats}) -> NStats = maps:update_with(Key, fun(V) -> V+I end, I, Stats), Session#session{deliver_stats = NStats}. diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 8b78961f3..99226ec6e 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -93,7 +93,9 @@ info(sockname, #state{sockname = Sockname}) -> info(sockstate, #state{sockstate = SockSt}) -> SockSt; info(channel, #state{channel = Channel}) -> - emqx_channel:info(Channel). + emqx_channel:info(Channel); +info(stop_reason, #state{stop_reason = Reason}) -> + Reason. -spec(stats(pid()|state()) -> emqx_types:stats()). stats(WsPid) when is_pid(WsPid) -> @@ -290,7 +292,14 @@ handle_call(From, Req, State = #state{channel = Channel}) -> %%-------------------------------------------------------------------- %% Handle Info -handle_info({enter, _}, State = #state{channel = Channel}) -> +handle_info({connack, ConnAck}, State = #state{channel = Channel}) -> + ChanAttrs = emqx_channel:attrs(Channel), + SockAttrs = maps:from_list(info(?INFO_KEYS, State)), + Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), + ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel), + reply(enqueue(ConnAck, State)); + +handle_info({enter, disconnected}, State = #state{channel = Channel}) -> ChanAttrs = emqx_channel:attrs(Channel), SockAttrs = maps:from_list(info(?INFO_KEYS, State)), Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index 7d8c62433..f8a910a47 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -51,21 +51,34 @@ end_per_suite(_Config) -> ok. init_per_testcase(_TestCase, Config) -> + %% CM Meck + ok = meck:new(emqx_cm, [passthrough, no_history]), + %% Access Control Meck + ok = meck:new(emqx_access_control, [passthrough, no_history]), + ok = meck:expect(emqx_access_control, authenticate, + fun(_) -> {ok, #{auth_result => success}} end), + ok = meck:expect(emqx_access_control, check_acl, fun(_, _, _) -> allow end), + %% Broker Meck ok = meck:new(emqx_broker, [passthrough, no_history]), + %% Hooks Meck ok = meck:new(emqx_hooks, [passthrough, no_history]), ok = meck:expect(emqx_hooks, run, fun(_Hook, _Args) -> ok end), ok = meck:expect(emqx_hooks, run_fold, fun(_Hook, _Args, Acc) -> Acc end), + %% Session Meck ok = meck:new(emqx_session, [passthrough, no_history]), + %% Metrics ok = meck:new(emqx_metrics, [passthrough, no_history]), ok = meck:expect(emqx_metrics, inc, fun(_) -> ok end), ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), Config. end_per_testcase(_TestCase, Config) -> + ok = meck:unload(emqx_access_control), ok = meck:unload(emqx_metrics), ok = meck:unload(emqx_session), ok = meck:unload(emqx_broker), ok = meck:unload(emqx_hooks), + ok = meck:unload(emqx_cm), Config. %%-------------------------------------------------------------------- @@ -106,9 +119,12 @@ t_chan_init(_) -> %%-------------------------------------------------------------------- t_handle_in_connect_packet_sucess(_) -> - ConnAck = ?CONNACK_PACKET(?RC_SUCCESS, 0, #{}), - {ok, {connack, ConnAck}, Channel} - = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), channel()), + ok = meck:expect(emqx_cm, open_session, + fun(true, _ClientInfo, _ConnInfo) -> + {ok, #{session => session(), present => false}} + end), + {ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS, 0)}], Channel} + = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), channel(#{conn_state => idle})), ClientInfo = emqx_channel:info(clientinfo, Channel), ?assertMatch(#{clientid := <<"clientid">>, username := <<"username">> @@ -117,8 +133,8 @@ t_handle_in_connect_packet_sucess(_) -> t_handle_in_unexpected_connect_packet(_) -> Channel = emqx_channel:set_field(conn_state, connected, channel()), - Result = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), Channel), - ?assertEqual({shutdown, protocol_error, Channel}, Result). + {shutdown, protocol_error, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), Channel} + = emqx_channel:handle_in(?CONNECT_PACKET(connpkt()), Channel). t_handle_in_qos0_publish(_) -> ok = meck:expect(emqx_broker, publish, fun(_) -> ok end), @@ -133,15 +149,16 @@ t_handle_in_qos1_publish(_) -> Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), {ok, ?PUBACK_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), - ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). + ?assertEqual(#{publish_in => 1, puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_qos2_publish(_) -> ok = meck:expect(emqx_session, publish, fun(_, _Msg, Session) -> {ok, [], Session} end), + ok = meck:expect(emqx_session, info, fun(awaiting_rel_timeout, _Session) -> 300000 end), Channel = channel(#{conn_state => connected}), Publish = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>), {ok, ?PUBREC_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), - ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). + ?assertEqual(#{publish_in => 1, pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_puback_ok(_) -> Msg = emqx_message:make(<<"t">>, <<"payload">>), @@ -156,18 +173,16 @@ t_handle_in_puback_id_in_use(_) -> fun(_, _Session) -> {error, ?RC_PACKET_IDENTIFIER_IN_USE} end), - Channel = channel(#{conn_state => connected}), - PubAck = ?PUBACK_PACKET(1, ?RC_SUCCESS), - {ok, Channel} = emqx_channel:handle_in(PubAck, Channel). + {ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()), + ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_puback_id_not_found(_) -> ok = meck:expect(emqx_session, puback, fun(_, _Session) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), - Channel = channel(#{conn_state => connected}), - PubAck = ?PUBACK_PACKET(1, ?RC_SUCCESS), - {ok, Channel} = emqx_channel:handle_in(PubAck, Channel). + {ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()), + ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrec_ok(_) -> Msg = emqx_message:make(test,?QOS_2, <<"t">>, <<"payload">>), @@ -183,18 +198,20 @@ t_handle_in_pubrec_id_in_use(_) -> fun(_, Session) -> {error, ?RC_PACKET_IDENTIFIER_IN_USE} end), - Channel = channel(#{conn_state => connected}), {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), Channel} - = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel). + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()), + ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrec_id_not_found(_) -> ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), - Channel = channel(#{conn_state => connected}), {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} - = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel). + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()), + ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrel_ok(_) -> ok = meck:expect(emqx_session, pubrel, fun(_, Session) -> {ok, Session} end), @@ -209,15 +226,13 @@ t_handle_in_pubrel_not_found_error(_) -> fun(_PacketId, _Session) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), - Channel = channel(#{conn_state => connected}), - {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} - = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel). + {ok, ?PUBCOMP_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), _Channel} + = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), channel()). t_handle_in_pubcomp_ok(_) -> ok = meck:expect(emqx_session, pubcomp, fun(_, Session) -> {ok, Session} end), - Channel = channel(#{conn_state => connected}), - {ok, Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)). + {ok, Channel} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), channel()), + ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_pubcomp_not_found_error(_) -> ok = meck:expect(emqx_session, pubcomp, @@ -270,35 +285,38 @@ t_handle_in_frame_error(_) -> {shutdown, frame_too_large, ?CONNACK_PACKET(?RC_MALFORMED_PACKET), _} = emqx_channel:handle_in({frame_error, frame_too_large}, ConnectingChan), ConnectedChan = channel(#{conn_state => connected}), - {shutdown, frame_too_large, ?DISCONNECT_PACKET(?RC_MALFORMED_PACKET), _} + {shutdown, malformed_Packet, ?DISCONNECT_PACKET(?RC_MALFORMED_PACKET), _} = emqx_channel:handle_in({frame_error, frame_too_large}, ConnectedChan), DisconnectedChan = channel(#{conn_state => disconnected}), {ok, DisconnectedChan} = emqx_channel:handle_in({frame_error, frame_too_large}, DisconnectedChan). +%% TODO: t_handle_in_expected_packet(_) -> - {ok, _Chan} = emqx_channel:handle_in(packet, channel()). + {shutdown, protocol_error, ?DISCONNECT_PACKET(?RC_PROTOCOL_ERROR), _Chan} + = emqx_channel:handle_in(packet, channel()). t_process_connect(_) -> ok = meck:expect(emqx_cm, open_session, fun(true, _ClientInfo, _ConnInfo) -> {ok, #{session => session(), present => false}} end), - ConnPkt = connpkt(), - {ok, ?CONNACK_PACKET(?RC_SUCCESS), ConnPkt, _} - = emqx_channel:process_connect(ConnPkt, channel()). + {ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS)}], _Channel} + = emqx_channel:process_connect(connpkt(), channel(#{conn_state => idle})). -t_handle_publish(_) -> - Publish = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _Channel} - = emqx_channel:handle_publish(Publish, channel()). +t_handle_publish_qos0(_) -> + ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), + Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), + {ok, _Channel} = emqx_channel:handle_publish(Publish, channel()). t_process_publish_qos1(_) -> + ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), Msg = emqx_message:make(test, ?QOS_1, <<"t">>, <<"payload">>), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _Channel} + {ok, ?PUBACK_PACKET(1, ?RC_NO_MATCHING_SUBSCRIBERS), _Channel} = emqx_channel:process_publish(1, Msg, channel()). t_process_subscribe(_) -> + ok = meck:expect(emqx_session, subscribe, fun(_, _, _, Session) -> {ok, Session} end), TopicFilters = [{<<"+">>, ?DEFAULT_SUBOPTS}], {[?RC_SUCCESS], _Channel} = emqx_channel:process_subscribe(TopicFilters, channel()). @@ -312,11 +330,17 @@ t_process_unsubscribe(_) -> %%-------------------------------------------------------------------- t_handle_out_delivers(_) -> - ok = emqx_meck:expect(emqx_session, deliver, - fun(Delivers, Session) -> - Msgs = [Msg || {deliver, _, Msg} <- Delivers], - [{publish, PacketId, Msg} || {PacketId, Msg} <- lists:zip(lists:seq(1, length(Msgs)), Msgs)] - end), + WithPacketId = fun(Msgs) -> + lists:zip(lists:seq(1, length(Msgs)), Msgs) + end, + ok = meck:expect(emqx_session, deliver, + fun(Delivers, Session) -> + Msgs = [Msg || {deliver, _, Msg} <- Delivers], + Publishes = [{publish, PacketId, Msg} + || {PacketId, Msg} <- WithPacketId(Msgs)], + {ok, Publishes, Session} + end), + ok = meck:expect(emqx_session, info, fun(retry_interval, _Session) -> 20000 end), Msg0 = emqx_message:make(test, ?QOS_1, <<"t1">>, <<"qos1">>), Msg1 = emqx_message:make(test, ?QOS_2, <<"t2">>, <<"qos2">>), Delivers = [{deliver, <<"+">>, Msg0}, {deliver, <<"+">>, Msg1}], @@ -334,7 +358,7 @@ t_handle_out_publishes(_) -> t_handle_out_publish(_) -> Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t">>, <<"payload">>), - {ok, ?PUBLISH_PACKET(?QOS_1, <<"t">>, <<"payload">>), _Channel} + {ok, ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), _Chan} = emqx_channel:handle_out({publish, 1, Msg}, channel()). t_handle_out_publish_nl(_) -> @@ -345,13 +369,12 @@ t_handle_out_publish_nl(_) -> {ok, Channel} = emqx_channel:handle_out(Publish, Channel). t_handle_out_connack_sucess(_) -> - Channel = channel(#{conn_state => connected}), - {ok, {connack, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}, _Chan} - = emqx_channel:handle_out({connack, ?RC_SUCCESS, 0, connpkt()}, Channel). + {ok, [{connack, ?CONNACK_PACKET(?RC_SUCCESS, SP, _)}], _Chan} + = emqx_channel:handle_out(connack, {?RC_SUCCESS, 0, connpkt()}, channel()). t_handle_out_connack_failure(_) -> {shutdown, not_authorized, ?CONNACK_PACKET(?RC_NOT_AUTHORIZED), _Chan} - = emqx_channel:handle_out({connack, ?RC_NOT_AUTHORIZED, connpkt()}, channel()). + = emqx_channel:handle_out(connack, {?RC_NOT_AUTHORIZED, connpkt()}, channel()). t_handle_out_puback(_) -> Channel = channel(#{conn_state => connected}), @@ -362,33 +385,33 @@ t_handle_out_puback(_) -> t_handle_out_pubrec(_) -> Channel = channel(#{conn_state => connected}), {ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), NChannel} - = emqx_channel:handle_out({pubrec, 1, ?RC_SUCCESS}, Channel), + = emqx_channel:handle_out(pubrec, {1, ?RC_SUCCESS}, Channel), ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_pubrel(_) -> Channel = channel(#{conn_state => connected}), {ok, ?PUBREL_PACKET(1), Channel1} - = emqx_channel:handle_out({pubrel, 1, ?RC_SUCCESS}, Channel), + = emqx_channel:handle_out(pubrel, {1, ?RC_SUCCESS}, Channel), {ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), Channel2} - = emqx_channel:handle_out({pubrel, 2, ?RC_SUCCESS}, Channel1), + = emqx_channel:handle_out(pubrel, {2, ?RC_SUCCESS}, Channel1), ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)). t_handle_out_pubcomp(_) -> {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel} - = emqx_channel:handle_out({pubcomp, 2, ?RC_SUCCESS}, channel()), + = emqx_channel:handle_out(pubcomp, {1, ?RC_SUCCESS}, channel()), ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_out_suback(_) -> {ok, ?SUBACK_PACKET(1, [?QOS_2]), _Channel} - = emqx_channel:handle_out({suback, 1, [?QOS_2]}, channel()). + = emqx_channel:handle_out(suback, {1, [?QOS_2]}, channel()). t_handle_out_unsuback(_) -> {ok, ?UNSUBACK_PACKET(1, [?RC_SUCCESS]), _Channel} - = emqx_channel:handle_out({unsuback, 1, [?RC_SUCCESS]}, channel()). + = emqx_channel:handle_out(unsuback, {1, [?RC_SUCCESS]}, channel()). t_handle_out_disconnect(_) -> {shutdown, normal, ?DISCONNECT_PACKET(?RC_SUCCESS), _Chan} - = emqx_channel:handle_out({disconnect, ?RC_SUCCESS}, channel()). + = emqx_channel:handle_out(disconnect, ?RC_SUCCESS, channel()). t_handle_out_unexpected(_) -> {ok, _Channel} = emqx_channel:handle_out(unexpected, <<"data">>, channel()). @@ -430,26 +453,40 @@ t_handle_info_unsubscribe(_) -> {ok, _Chan} = emqx_channel:handle_info({unsubscribe, topic_filters()}, channel()). t_handle_info_sock_closed(_) -> - {ok, _Chan} = emqx_channel:handle_out({sock_closed, reason}, channel(#{conn_state => disconnected})). + {ok, _Chan} = emqx_channel:handle_out({sock_closed, reason}, + channel(#{conn_state => disconnected})). %%-------------------------------------------------------------------- %% Test cases for handle_timeout %%-------------------------------------------------------------------- t_handle_timeout_emit_stats(_) -> - {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), {emit_stats, []}, channel()). + ok = meck:expect(emqx_cm, set_chan_stats, fun(_, _) -> ok end), + TRef = make_ref(), + Channel = emqx_channel:set_field(timers, #{stats_timer => TRef}, channel()), + {ok, _Chan} = emqx_channel:handle_timeout(TRef, {emit_stats, []}, Channel). t_handle_timeout_keepalive(_) -> + TRef = make_ref(), + Channel = emqx_channel:set_field(timers, #{alive_timer => TRef}, channel()), {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), {keepalive, 10}, channel()). t_handle_timeout_retry_delivery(_) -> - {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), retry_delivery, channel()). + ok = meck:expect(emqx_session, retry, fun(Session) -> {ok, Session} end), + TRef = make_ref(), + Channel = emqx_channel:set_field(timers, #{retry_timer => TRef}, channel()), + {ok, _Chan} = emqx_channel:handle_timeout(TRef, retry_delivery, channel()). t_handle_timeout_expire_awaiting_rel(_) -> - {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), expire_awaiting_rel, channel()). + ok = meck:expect(emqx_session, expire, fun(_, Session) -> {ok, Session} end), + TRef = make_ref(), + Channel = emqx_channel:set_field(timers, #{await_timer => TRef}, channel()), + {ok, _Chan} = emqx_channel:handle_timeout(TRef, expire_awaiting_rel, Channel). t_handle_timeout_expire_session(_) -> - {shutdown, expired, _Chan} = emqx_channel:handle_timeout(make_ref(), expire_awaiting_rel, channel()). + TRef = make_ref(), + Channel = emqx_channel:set_field(timers, #{expire_timer => TRef}, channel()), + {shutdown, expired, _Chan} = emqx_channel:handle_timeout(TRef, expire_session, Channel). t_handle_timeout_will_message(_) -> {ok, _Chan} = emqx_channel:handle_timeout(make_ref(), will_message, channel()). @@ -471,7 +508,6 @@ t_check_flapping(_) -> ok = emqx_channel:check_flapping(connpkt(), channel()). t_auth_connect(_) -> - ok = meck:expect(emqx_access_control, authenticate, fun(_) -> {ok, #{}} end), {ok, _Chan} = emqx_channel:auth_connect(connpkt(), channel()). t_process_alias(_) -> @@ -481,15 +517,22 @@ t_process_alias(_) -> = emqx_channel:process_alias(#mqtt_packet{variable = Publish}, Channel). t_check_pub_acl(_) -> + ok = meck:new(emqx_zone, [passthrough, no_history]), + ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), Publish = ?PUBLISH_PACKET(?QOS_0, <<"t">>, 1, <<"payload">>), - ok = emqx_channel:check_pub_acl(Publish, channel()). + ok = emqx_channel:check_pub_acl(Publish, channel()), + ok = meck:unload(emqx_zone). t_check_pub_alias(_) -> Publish = #mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias' => 1}}, - ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, channel()). + Channel = emqx_channel:set_field(alias_maximum, #{inbound => 10}, channel()), + ok = emqx_channel:check_pub_alias(#mqtt_packet{variable = Publish}, Channel). t_check_subscribe(_) -> - ok = emqx_channel:check_subscribe(<<"t">>, ?DEFAULT_SUBOPTS, channel()). + ok = meck:new(emqx_zone, [passthrough, no_history]), + ok = meck:expect(emqx_zone, enable_acl, fun(_) -> true end), + ok = emqx_channel:check_subscribe(<<"t">>, ?DEFAULT_SUBOPTS, channel()), + ok = meck:unload(emqx_zone). t_enrich_caps(_) -> ok = meck:new(emqx_mqtt_caps, [passthrough, no_history]), diff --git a/test/emqx_session_SUITE.erl b/test/emqx_session_SUITE.erl index 1fb41576f..621fe4aa5 100644 --- a/test/emqx_session_SUITE.erl +++ b/test/emqx_session_SUITE.erl @@ -48,7 +48,7 @@ end_per_testcase(_TestCase, Config) -> %%-------------------------------------------------------------------- t_session_init(_) -> - Session = emqx_session:init(#{zone => external}, #{receive_maximum => 64}), + Session = emqx_session:init(#{zone => zone}, #{receive_maximum => 64}), ?assertEqual(#{}, emqx_session:info(subscriptions, Session)), ?assertEqual(0, emqx_session:info(subscriptions_cnt, Session)), ?assertEqual(0, emqx_session:info(subscriptions_max, Session)), @@ -67,30 +67,28 @@ t_session_init(_) -> %%-------------------------------------------------------------------- t_session_info(_) -> + Info = emqx_session:info(session()), ?assertMatch(#{subscriptions := #{}, subscriptions_max := 0, upgrade_qos := false, - inflight := 0, - inflight_max := 64, + inflight_max := 0, retry_interval := 0, mqueue_len := 0, mqueue_max := 1000, mqueue_dropped := 0, next_pkt_id := 1, - awaiting_rel := 0, - awaiting_rel_max := 0, - await_rel_timeout := 3600000 - }, emqx_session:info(session())). + awaiting_rel := #{}, + awaiting_rel_max := 100, + awaiting_rel_timeout := 3600000 + }, Info). t_session_attrs(_) -> Attrs = emqx_session:attrs(session()), - io:format("~p~n", [Attrs]), - error('TODO'). + io:format("~p~n", [Attrs]). t_session_stats(_) -> Stats = emqx_session:stats(session()), - io:format("~p~n", [Stats]), - error('TODO'). + io:format("~p~n", [Stats]). %%-------------------------------------------------------------------- %% Test cases for pub/sub @@ -125,7 +123,7 @@ t_publish_qos2(_) -> ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), Msg = emqx_message:make(test, ?QOS_2, <<"t">>, <<"payload">>), {ok, [], Session} = emqx_session:publish(1, Msg, session()), - ?assertEqual(awaiting_rel_cnt, emqx_session:info(awaiting_rel_cnt, Session)). + ?assertEqual(1, emqx_session:info(awaiting_rel_cnt, Session)). t_publish_qos1(_) -> ok = meck:expect(emqx_broker, publish, fun(_) -> [] end), @@ -151,7 +149,7 @@ t_puback(_) -> Inflight = emqx_inflight:insert(1, {Msg, os:timestamp()}, emqx_inflight:new()), Session = set_field(inflight, Inflight, session()), {ok, Msg, NSession} = emqx_session:puback(1, Session), - ?assertEqual([], emqx_session:info(inflight, NSession)). + ?assertEqual(0, emqx_session:info(inflight_cnt, NSession)). t_puback_error_packet_id_in_use(_) -> Inflight = emqx_inflight:insert(1, {pubrel, os:timestamp()}, emqx_inflight:new()), @@ -166,14 +164,14 @@ t_pubrec(_) -> Inflight = emqx_inflight:insert(2, {Msg, os:timestamp()}, emqx_inflight:new()), Session = set_field(inflight, Inflight, session()), {ok, Msg, NSession} = emqx_session:pubrec(2, Session), - ?assertMatch([{pubrel, _}], emqx_session:info(inflight, NSession)). + ?assertMatch([{pubrel, _}], emqx_inflight:values(emqx_session:info(inflight, NSession))). -t_pubrec_error_packet_id_in_use(_) -> +t_pubrec_packet_id_in_use_error(_) -> Inflight = emqx_inflight:insert(1, {pubrel, ts()}, emqx_inflight:new()), Session = set_field(inflight, Inflight, session()), - {error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, session()). + {error, ?RC_PACKET_IDENTIFIER_IN_USE} = emqx_session:puback(1, Session). -t_pubrec_error_packet_id_not_found(_) -> +t_pubrec_packet_id_not_found_error(_) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubrec(1, session()). t_pubrel(_) -> @@ -188,7 +186,7 @@ t_pubcomp(_) -> Inflight = emqx_inflight:insert(2, {pubrel, os:timestamp()}, emqx_inflight:new()), Session = emqx_session:set_field(inflight, Inflight, session()), {ok, NSession} = emqx_session:pubcomp(2, Session), - ?assertEqual([], emqx_session:info(inflight, NSession)). + ?assertEqual(0, emqx_session:info(inflight_cnt, NSession)). t_pubcomp_id_not_found(_) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} = emqx_session:pubcomp(2, session()). @@ -200,36 +198,39 @@ t_pubcomp_id_not_found(_) -> t_dequeue(_) -> {ok, Session} = emqx_session:dequeue(session()). -t_bach_n(_) -> - error('TODO'). - -t_dequeue_with_msgs(_) -> - error('TODO'). - t_deliver(_) -> - error('TODO'). + Delivers = [delivery(?QOS_1, <<"t1">>), delivery(?QOS_2, <<"t2">>)], + {ok, Publishes, _Session} = emqx_session:deliver(Delivers, session()), + ?assertEqual(2, length(Publishes)). t_enqueue(_) -> - error('TODO'). + Delivers = [delivery(?QOS_1, <<"t1">>), delivery(?QOS_2, <<"t2">>)], + Session = emqx_session:enqueue(Delivers, session()), + ?assertEqual(2, emqx_session:info(mqueue_len, Session)). t_retry(_) -> - error('TODO'). + {ok, _Session} = emqx_session:retry(session()). %%-------------------------------------------------------------------- %% Test cases for takeover/resume %%-------------------------------------------------------------------- t_takeover(_) -> - error('TODO'). + ok = meck:expect(emqx_broker, unsubscribe, fun(_) -> ok end), + Session = session(#{subscriptions => #{<<"t">> => ?DEFAULT_SUBOPTS}}), + ok = emqx_session:takeover(Session). t_resume(_) -> - error('TODO'). + ok = meck:expect(emqx_broker, subscribe, fun(_, _, _) -> ok end), + Subs = #{<<"t">> => ?DEFAULT_SUBOPTS}, + Session = session(#{subscriptions => #{<<"t">> => ?DEFAULT_SUBOPTS}}), + ok = emqx_session:resume(<<"clientid">>, Session). t_redeliver(_) -> - error('TODO'). + {ok, [], _Session} = emqx_session:redeliver(session()). t_expire(_) -> - error('TODO'). + {ok, _Session} = emqx_session:expire(awaiting_rel, session()). %%-------------------------------------------------------------------- %% Helper functions @@ -254,5 +255,8 @@ subopts() -> subopts(#{}). subopts(Init) -> maps:merge(?DEFAULT_SUBOPTS, Init). +delivery(QoS, Topic) -> + {deliver, Topic, emqx_message:make(test, QoS, Topic, <<"payload">>)}. + ts() -> erlang:system_time(second). diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl index fa036335f..5806f6f73 100644 --- a/test/emqx_ws_connection_SUITE.erl +++ b/test/emqx_ws_connection_SUITE.erl @@ -120,8 +120,8 @@ t_websocket_handle_ping_pong(_) -> t_websocket_handle_bad_frame(_) -> with_ws_conn(fun(WsConn) -> - {stop, {shutdown, unexpected_ws_frame}, WsConn} - = websocket_handle({badframe, <<>>}, WsConn) + {stop, WsConn1} = websocket_handle({badframe, <<>>}, WsConn), + ?assertEqual({shutdown, unexpected_ws_frame}, stop_reason(WsConn1)) end). t_websocket_info_call(_) -> @@ -132,11 +132,11 @@ t_websocket_info_call(_) -> end). t_websocket_info_cast(_) -> - with_ws_conn(fun(WsConn) -> - websocket_info({cast, msg}, WsConn) - end). + ok = meck:expect(emqx_channel, handle_info, fun(_Msg, Channel) -> {ok, Channel} end), + with_ws_conn(fun(WsConn) -> websocket_info({cast, msg}, WsConn) end). t_websocket_info_incoming(_) -> + ok = meck:expect(emqx_channel, handle_in, fun(_Packet, Channel) -> {ok, Channel} end), with_ws_conn(fun(WsConn) -> Connect = ?CONNECT_PACKET( #mqtt_packet_connect{proto_ver = ?MQTT_PROTO_V5, @@ -146,14 +146,18 @@ t_websocket_info_incoming(_) -> keepalive = 60}), {ok, WsConn1} = websocket_info({incoming, Connect}, WsConn), Publish = ?PUBLISH_PACKET(?QOS_1, <<"t">>, 1, <<"payload">>), - {ok, WsConn2} = websocket_info({incoming, Publish}, WsConn1) + {ok, _WsConn2} = websocket_info({incoming, Publish}, WsConn1) end). t_websocket_info_deliver(_) -> with_ws_conn(fun(WsConn) -> - Msg = emqx_message:make(<<"topic">>, <<"payload">>), - Deliver = {deliver, <<"#">>, Msg}, - {ok, WsConn1} = websocket_info(Deliver, WsConn) + ok = meck:expect(emqx_channel, handle_out, + fun(Delivers, Channel) -> + Packets = [emqx_message:to_packet(1, Msg) || {deliver, _, Msg} <- Delivers], + {ok, {outgoing, Packets}, Channel} + end), + Deliver = {deliver, <<"#">>, emqx_message:make(<<"topic">>, <<"payload">>)}, + {reply, {binary, _Data}, _WsConn1} = websocket_info(Deliver, WsConn) end). t_websocket_info_timeout(_) -> @@ -165,23 +169,31 @@ t_websocket_info_timeout(_) -> t_websocket_info_close(_) -> with_ws_conn(fun(WsConn) -> - {stop, {shutdown, sock_error}, WsConn} = websocket_info({close, sock_error}, WsConn) + {stop, WsConn1} = websocket_info({close, sock_error}, WsConn), + ?assertEqual({shutdown, sock_error}, stop_reason(WsConn1)) end). t_websocket_info_shutdown(_) -> with_ws_conn(fun(WsConn) -> - {stop, {shutdown, reason}, WsConn} = websocket_info({shutdown, reason}, WsConn) + {stop, WsConn1} = websocket_info({shutdown, reason}, WsConn), + ?assertEqual({shutdown, reason}, stop_reason(WsConn1)) end). + t_websocket_info_stop(_) -> with_ws_conn(fun(WsConn) -> - {stop, normal, WsConn} = websocket_info({stop, normal}, WsConn) + {stop, WsConn1} = websocket_info({stop, normal}, WsConn), + ?assertEqual(normal, stop_reason(WsConn1)) end). t_websocket_close(_) -> + ok = meck:expect(emqx_channel, handle_info, + fun({sock_closed, badframe}, Channel) -> + {shutdown, sock_closed, Channel} + end), with_ws_conn(fun(WsConn) -> - {stop, sock_closed, WsConn} - = emqx_ws_connection:websocket_close(badframe, WsConn) + {stop, WsConn1} = emqx_ws_connection:websocket_close(badframe, WsConn), + ?assertEqual(sock_closed, stop_reason(WsConn1)) end). t_handle_call(_) -> @@ -217,3 +229,6 @@ with_ws_conn(TestFun, Opts) -> [req, emqx_misc:merge_opts([{zone, external}], Opts)]), TestFun(WsConn). +stop_reason(WsConn) -> + emqx_ws_connection:info(stop_reason, WsConn). + From cb77b229b827f71875dc36a7e28f9a636c390ece Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Mon, 21 Oct 2019 18:32:14 +0800 Subject: [PATCH 07/40] Add testcase for emqx_logger_formatter --- src/emqx_logger_formatter.erl | 20 +- test/emqx_logger_formatter_SUITE.erl | 662 +++++++++++++++++++++++++-- 2 files changed, 631 insertions(+), 51 deletions(-) diff --git a/src/emqx_logger_formatter.erl b/src/emqx_logger_formatter.erl index 6cfe1ca58..ad5c9cef7 100644 --- a/src/emqx_logger_formatter.erl +++ b/src/emqx_logger_formatter.erl @@ -79,7 +79,7 @@ format(#{level:=Level,msg:=Msg0,meta:=Meta},Config0) end, Config#{chars_limit=>Size} end, - string:trim(format_msg(Msg0,Meta,Config1)); + format_msg(Msg0,Meta,Config1); true -> "" end, @@ -138,7 +138,7 @@ to_string(X,_) when is_list(X) -> _ -> io_lib:format(?FormatP,[X]) end; to_string(X,_) -> - io_lib:format("~s",[X]). + io_lib:format(?FormatP,[X]). printable_list([]) -> false; @@ -194,14 +194,14 @@ do_format_msg({Format0,Args},Depth,Opts) -> Format = reformat(Format1, Depth), io_lib:build_text(Format,Opts) catch C:R:S -> - FormatError = "FORMAT ERROR: ~0tp - ~0tp", - case Format0 of - FormatError -> - %% already been here - avoid failing cyclically - erlang:raise(C,R,S); - _ -> - format_msg({FormatError,[Format0,Args]},Depth,Opts) - end + FormatError = "FORMAT ERROR: ~0tp - ~0tp", + case Format0 of + FormatError -> + %% already been here - avoid failing cyclically + erlang:raise(C,R,S); + _ -> + do_format_msg({FormatError,[Format0,Args]},Depth,Opts) + end end. reformat(Format,unlimited) -> diff --git a/test/emqx_logger_formatter_SUITE.erl b/test/emqx_logger_formatter_SUITE.erl index ea8180e9e..a135c7bc9 100644 --- a/test/emqx_logger_formatter_SUITE.erl +++ b/test/emqx_logger_formatter_SUITE.erl @@ -1,5 +1,7 @@ -%%-------------------------------------------------------------------- -%% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. 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. @@ -12,16 +14,21 @@ %% 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. -%%-------------------------------------------------------------------- - +%% +%% %CopyrightEnd% +%% -module(emqx_logger_formatter_SUITE). -compile(export_all). --compile(nowarn_export_all). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). +-include_lib("kernel/include/logger.hrl"). -all() -> emqx_ct:all(?MODULE). +-define(TRY(X), my_try(fun() -> X end)). + +suite() -> + [{timetrap,{seconds,30}}]. init_per_suite(Config) -> Config. @@ -29,45 +36,618 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -t_chars_limit(_Config) -> - CharsLimit = 50, - LogFilename = "./t_chars_limit.log", - Formatter = {emqx_logger_formatter, - #{chars_limit => CharsLimit}}, - #{level := OrgLevel} = logger:get_primary_config(), - Config = - #{level => info, - config => #{ - type => halt, - file => LogFilename}, - formatter => Formatter}, - logger:add_handler(t_handler, logger_disk_log_h, Config), - logger:set_primary_config(level, info), +init_per_group(_Group, Config) -> + Config. - logger:info("hello"), - logger:info(lists:duplicate(10, "hello")), - logger_disk_log_h:filesync(t_handler), +end_per_group(_Group, _Config) -> + ok. - ct:pal("content : ~p", [file:read_file(LogFilename)]), - [FirstLine, SecondLine] = readlines(LogFilename), +init_per_testcase(_TestCase, Config) -> + Config. - ?assertMatch([_Date, _Time, _Level, "hello\n"], string:split(FirstLine, " ", all)), - ?assert(length(SecondLine) =< 50), +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. - logger:set_primary_config(level, OrgLevel). +groups() -> + []. + +all() -> + [default, + single_line, + template, + format_msg, + report_cb, + max_size, + depth, + chars_limit, + format_mfa, + level_or_msg_in_meta, + faulty_log, + faulty_config, + faulty_msg, + check_config, + update_config]. + +default(_Config) -> + String1 = format(info,{"~p",[term]},#{},#{}), + ct:log(String1), + ?assertMatch([_Date, _Time,"info:","term\n"], string:lexemes(String1," ")), + + Time = timestamp(), + ExpectedTimestamp = default_time_format(Time), + String2 = format(info,{"~p",[term]},#{time=>Time},#{}), + ct:log("ExpectedTimestamp: ~p, got: ~p", [ExpectedTimestamp, String2]), + " info: term\n" = string:prefix(String2,ExpectedTimestamp), + ok. + +single_line(_Config) -> + Time = timestamp(), + ExpectedTimestamp = default_time_format(Time), + String1 = format(info,{"~p",[term]},#{time=>Time},#{}), + ct:log(String1), + ?assertMatch(" info: term\n", string:prefix(String1,ExpectedTimestamp)), + + String2 = format(info,{"a:~n~p",[term]},#{time=>Time},#{}), + ct:log(String2), + ?assertMatch(" info: a:\nterm\n", string:prefix(String2,ExpectedTimestamp)), -readlines(FileName) -> - {ok, Device} = file:open(FileName, [read]), - try get_all_lines(Device) - after file:close(Device) - end. + Prefix = + "Some characters to fill the line ------------------------------------- ", + %% There would actually be newlines inside the + %% list and map. + String4 = format(info,{"~s~p~n~s~p~n",[Prefix, + lists:seq(1,10), + Prefix, + #{a=>map,with=>a,few=>accociations}]}, + #{time=>Time}, + #{}), + ct:log(String4), + match = re:run(String4,"\\[1,2,3,\n",[global,{capture,none}]), + {match,Match4} = re:run(String4,"=>\n",[global,{capture,all}]), + 3 = length(Match4), -get_all_lines(Device) -> - get_all_lines(Device, []). -get_all_lines(Device, All) -> - case io:get_line(Device, "") of - eof -> - lists:reverse(All); - Line -> get_all_lines(Device, [Line | All]) - end. \ No newline at end of file + %% Test that big metadata fields do not get line breaks + String5 = format(info,"", + #{mymeta=>lists:seq(1,100)}, + #{template=>[mymeta,"\n"]}), + ct:log(String5), + [_] = string:lexemes(String5,"\n"), + ok. + +template(_Config) -> + Time = timestamp(), + + Template1 = [msg], + String1 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template1}), + ct:log(String1), + "term" = String1, + + Template2 = [msg,unknown], + String2 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template2}), + ct:log(String2), + "term" = String2, + + Template3 = ["string"], + String3 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template3}), + ct:log(String3), + "string" = String3, + + Template4 = ["string\nnewline"], + String4 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template4}), + ct:log(String4), + "string\nnewline" = String4, + + Template5 = [], + String5 = format(info,{"~p",[term]},#{time=>Time},#{template=>Template5}), + ct:log(String5), + "" = String5, + + Ref6 = erlang:make_ref(), + Meta6 = #{atom=>some_atom, + integer=>632, + list=>[list,"string",4321,#{},{tuple}], + mfa=>{mod,func,0}, + pid=>self(), + ref=>Ref6, + string=>"some string", + time=>Time, + tuple=>{1,atom,"list"}, + nested=>#{subkey=>subvalue}}, + Template6 = lists:join(";",lists:sort(maps:keys(maps:remove(nested,Meta6))) ++ + [[nested,subkey]]), + String6 = format(info,{"~p",[term]},Meta6,#{template=>Template6}), + ct:log(String6), + SelfStr = pid_to_list(self()), + RefStr6 = ref_to_list(Ref6), + ListStr = "[list,\"string\",4321,#{},{tuple}]", + ExpectedTime6 = default_time_format(Time), + ["some_atom", + "632", + ListStr, + "mod:func/0", + SelfStr, + RefStr6, + "some string", + ExpectedTime6, + "{1,atom,\"list\"}", + "subvalue"] = string:lexemes(String6,";"), + + Meta7 = #{time=>Time, + nested=>#{key1=>#{subkey1=>value1}, + key2=>value2}}, + Template7 = lists:join(";",[nested, + [nested,key1], + [nested,key1,subkey1], + [nested,key2], + [nested,key2,subkey2], + [nested,key3], + [nested,key3,subkey3]]), + String7 = format(info,{"~p",[term]},Meta7,#{template=>Template7}), + ct:log(String7), + [MultipleKeysStr7, + "#{subkey1 => value1}", + "value1", + "value2", + "", + "", + ""] = string:split(String7,";",all), + %% Order of keys is not fixed + case MultipleKeysStr7 of + "#{key2 => value2,key1 => #{subkey1 => value1}}" -> ok; + "#{key1 => #{subkey1 => value1},key2 => value2}" -> ok; + _ -> ct:fail({full_nested_map_unexpected,MultipleKeysStr7}) + end, + + Meta8 = #{time=>Time, + nested=>#{key1=>#{subkey1=>value1}, + key2=>value2}}, + Template8 = + lists:join( + ";", + [{nested,["exist:",nested],["noexist"]}, + {[nested,key1],["exist:",[nested,key1]],["noexist"]}, + {[nested,key1,subkey1],["exist:",[nested,key1,subkey1]],["noexist"]}, + {[nested,key2],["exist:",[nested,key2]],["noexist"]}, + {[nested,key2,subkey2],["exist:",[nested,key2,subkey2]],["noexist"]}, + {[nested,key3],["exist:",[nested,key3]],["noexist"]}, + {[nested,key3,subkey3],["exist:",[nested,key3,subkey3]],["noexist"]}]), + String8 = format(info,{"~p",[term]},Meta8,#{template=>Template8}), + ct:log(String8), + [MultipleKeysStr8, + "exist:#{subkey1 => value1}", + "exist:value1", + "exist:value2", + "noexist", + "noexist", + "noexist"] = string:split(String8,";",all), + %% Order of keys is not fixed + case MultipleKeysStr8 of + "exist:#{key2 => value2,key1 => #{subkey1 => value1}}" -> ok; + "exist:#{key1 => #{subkey1 => value1},key2 => value2}" -> ok; + _ -> ct:fail({full_nested_map_unexpected,MultipleKeysStr8}) + end, + + ok. + +format_msg(_Config) -> + Template = [msg], + + String1 = format(info,{"~p",[term]},#{},#{template=>Template}), + ct:log(String1), + "term" = String1, + + String2 = format(info,{"list",[term]},#{},#{template=>Template}), + ct:log(String2), + "FORMAT ERROR: \"list\" - [term]" = String2, + + String3 = format(info,{report,term},#{},#{template=>Template}), + ct:log(String3), + "term" = String3, + + String4 = format(info,{report,term}, + #{report_cb=>fun(_)-> {"formatted",[]} end}, + #{template=>Template}), + ct:log(String4), + "formatted" = String4, + + String5 = format(info,{report,term}, + #{report_cb=>fun(_)-> faulty_return end}, + #{template=>Template}), + ct:log(String5), + "REPORT_CB/1 ERROR: term; Returned: faulty_return" = String5, + + String6 = format(info,{report,term}, + #{report_cb=>fun(_)-> erlang:error(fun_crashed) end}, + #{template=>Template}), + ct:log(String6), + "REPORT_CB/1 CRASH: term; Reason: {error,fun_crashed,"++_ = String6, + + String7 = format(info,{report,term}, + #{report_cb=>fun(_,_)-> ['not',a,string] end}, + #{template=>Template}), + ct:log(String7), + "REPORT_CB/2 ERROR: term; Returned: ['not',a,string]" = String7, + + String8 = format(info,{report,term}, + #{report_cb=>fun(_,_)-> faulty_return end}, + #{template=>Template}), + ct:log(String8), + "REPORT_CB/2 ERROR: term; Returned: faulty_return" = String8, + + String9 = format(info,{report,term}, + #{report_cb=>fun(_,_)-> erlang:error(fun_crashed) end}, + #{template=>Template}), + ct:log(String9), + "REPORT_CB/2 CRASH: term; Reason: {error,fun_crashed,"++_ = String9, + + %% strings are not formatted + String10 = format(info,{string,"string"}, + #{report_cb=>fun(_)-> {"formatted",[]} end}, + #{template=>Template}), + ct:log(String10), + "string" = String10, + + String11 = format(info,{string,['not',printable,list]}, + #{report_cb=>fun(_)-> {"formatted",[]} end}, + #{template=>Template}), + ct:log("~ts",[String11]), % avoiding ct_log crash + "FORMAT ERROR: \"~ts\" - [['not',printable,list]]" = String11, + + String12 = format(info,{string,"string"},#{},#{template=>Template}), + ct:log(String12), + "string" = String12, + + ok. + +report_cb(_Config) -> + Template = [msg], + MetaFun = fun(_) -> {"meta_rcb",[]} end, + ConfigFun = fun(_) -> {"config_rcb",[]} end, + "term" = format(info,{report,term},#{},#{template=>Template}), + "meta_rcb" = + format(info,{report,term},#{report_cb=>MetaFun},#{template=>Template}), + "config_rcb" = + format(info,{report,term},#{},#{template=>Template, + report_cb=>ConfigFun}), + "config_rcb" = + format(info,{report,term},#{report_cb=>MetaFun},#{template=>Template, + report_cb=>ConfigFun}), + ok. + +max_size(_Config) -> + Cfg = #{template=>[msg]}, + "12345678901234567890" = format(info,{"12345678901234567890",[]},#{},Cfg), + %% application:set_env(kernel,logger_max_size,11), + %% "12345678901234567890" = % min value is 50, so this is not limited + %% format(info,{"12345678901234567890",[]},#{},Cfg), + %% "12345678901234567890123456789012345678901234567..." = % 50 + %% format(info, + %% {"123456789012345678901234567890123456789012345678901234567890", + %% []}, + %% #{}, + %% Cfg), + %% application:set_env(kernel,logger_max_size,53), + %% "12345678901234567890123456789012345678901234567890..." = %53 + %% format(info, + %% {"123456789012345678901234567890123456789012345678901234567890", + %% []}, + %% #{}, + %% Cfg), + "123456789012..." = + format(info,{"12345678901234567890",[]},#{},Cfg#{max_size=>15}), + "12345678901234567890" = + format(info,{"12345678901234567890",[]},#{},Cfg#{max_size=>unlimited}), + %% Check that one newline at the end of the line is kept (if it exists) + "12345678901...\n" = + format(info,{"12345678901234567890\n",[]},#{},Cfg#{max_size=>15}), + "12345678901...\n" = + format(info,{"12345678901234567890",[]},#{},Cfg#{template=>[msg,"\n"], + max_size=>15}), + ok. +max_size(cleanup,_Config) -> + application:unset_env(kernel,logger_max_size), + ok. + +depth(_Config) -> + Template = [msg], + "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]" = + format(info, + {"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]}, + #{}, + #{template=>Template}), + application:set_env(kernel,error_logger_format_depth,11), + "[1,2,3,4,5,6,7,8,9,0|...]" = + format(info, + {"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]}, + #{}, + #{template=>Template}), + "[1,2,3,4,5,6,7,8,9,0,1,2|...]" = + format(info, + {"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]}, + #{}, + #{template=>Template, + depth=>13}), + "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]" = + format(info, + {"~p",[[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]]}, + #{}, + #{template=>Template, + depth=>unlimited}), + ok. +depth(cleanup,_Config) -> + application:unset_env(kernel,error_logger_format_depth), + ok. + +chars_limit(_Config) -> + FA = {"LoL: ~p~nL: ~p~nMap: ~p~n", + [lists:duplicate(10,lists:seq(1,100)), + lists:seq(1,100), + maps:from_list(lists:zip(lists:seq(1,100), + lists:duplicate(100,value)))]}, + Meta = #{time=>timestamp()}, + Template = [time," - ", msg, "\n"], + FC = #{template=>Template, + depth=>unlimited, + max_size=>unlimited, + chars_limit=>unlimited}, + CL1 = 80, + String1 = format(info,FA,Meta,FC#{chars_limit=>CL1}), + L1 = string:length(String1), + ct:log("String1: ~p~nLength1: ~p~n",[lists:flatten(String1),L1]), + true = L1 > CL1, + true = L1 < CL1 + 15, + + String2 = format(info,FA,Meta,FC#{chars_limit=>CL1,depth=>10}), + L2 = string:length(String2), + ct:log("String2: ~p~nLength2: ~p~n",[lists:flatten(String2),L2]), + String2 = String1, + + CL3 = 200, + String3 = format(info,FA,Meta,FC#{chars_limit=>CL3}), + L3 = string:length(String3), + ct:log("String3: ~p~nLength3: ~p~n",[lists:flatten(String3),L3]), + true = L3 > CL3, + true = L3 < CL3 + 15, + + String4 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10}), + L4 = string:length(String4), + ct:log("String4: ~p~nLength4: ~p~n",[lists:flatten(String4),L4]), + true = L4 > CL3, + true = L4 < CL3 + 15, + + %% Test that max_size truncates the string which is limited by + %% depth and chars_limit + MS5 = 150, + String5 = format(info,FA,Meta,FC#{chars_limit=>CL3,depth=>10,max_size=>MS5}), + L5 = string:length(String5), + ct:log("String5: ~p~nLength5: ~p~n",[String5,L5]), + L5 = MS5, + true = lists:prefix(lists:sublist(String5,L5-4),String4), + + %% Test that chars_limit limits string also + Str = "123456789012345678901234567890123456789012345678901234567890123456789", + CL6 = 80, + String6 = format(info,{string,Str},Meta,FC#{chars_limit=>CL6}), + L6 = string:length(String6), + ct:log("String6: ~p~nLength6: ~p~n",[String6,L6]), + L6 = CL6, + + ok. + +format_mfa(_Config) -> + Template = [mfa], + + Meta1 = #{mfa=>{mod,func,0}}, + String1 = format(info,{"~p",[term]},Meta1,#{template=>Template}), + ct:log(String1), + "mod:func/0" = String1, + + Meta2 = #{mfa=>{mod,func,[]}}, + String2 = format(info,{"~p",[term]},Meta2,#{template=>Template}), + ct:log(String2), + "mod:func/0" = String2, + + Meta3 = #{mfa=>"mod:func/0"}, + String3 = format(info,{"~p",[term]},Meta3,#{template=>Template}), + ct:log(String3), + "mod:func/0" = String3, + + Meta4 = #{mfa=>othermfa}, + String4 = format(info,{"~p",[term]},Meta4,#{template=>Template}), + ct:log(String4), + "othermfa" = String4, + + ok. + +level_or_msg_in_meta(_Config) -> + %% The template contains atoms to pick out values from meta, + %% or level/msg to add these from the log event. What if you have + %% a key named 'level' or 'msg' in meta and want to display + %% its value? + %% For now we simply ignore Meta on this and display the + %% actual level and msg from the log event. + + Meta = #{level=>mylevel, + msg=>"metamsg"}, + Template = [level,";",msg], + String = format(info,{"~p",[term]},Meta,#{template=>Template}), + ct:log(String), + "info;term" = String, % so mylevel and "metamsg" are ignored + ok. + +faulty_log(_Config) -> + %% Unexpected log (should be type logger:log_event()) - print error + {error, + function_clause, + {emqx_logger_formatter,format,[_,_],_}} = + ?TRY(emqx_logger_formatter:format(unexp_log,#{})), + ok. + +faulty_config(_Config) -> + {error, + function_clause, + {emqx_logger_formatter,format,[_,_],_}} = + ?TRY(emqx_logger_formatter:format(#{level=>info, + msg=>{"~p",[term]}, + meta=>#{time=>timestamp()}}, + unexp_config)), + ok. + +faulty_msg(_Config) -> + {error, + function_clause, + {emqx_logger_formatter,_,_,_}} = + ?TRY(emqx_logger_formatter:format(#{level=>info, + msg=>term, + meta=>#{time=>timestamp()}}, + #{})), + ok. + +-define(cfgerr(X), {error,{invalid_formatter_config,emqx_logger_formatter,X}}). +check_config(_Config) -> + ok = emqx_logger_formatter:check_config(#{}), + ?cfgerr(bad) = emqx_logger_formatter:check_config(bad), + + C1 = #{chars_limit => 1, + depth => 1, + max_size => 1, + report_cb => fun(R) -> {"~p",[R]} end, + template => []}, + ok = emqx_logger_formatter:check_config(C1), + + ok = emqx_logger_formatter:check_config(#{chars_limit => unlimited}), + ?cfgerr({chars_limit,bad}) = + emqx_logger_formatter:check_config(#{chars_limit => bad}), + + ok = emqx_logger_formatter:check_config(#{depth => unlimited}), + ?cfgerr({depth,bad}) = + emqx_logger_formatter:check_config(#{depth => bad}), + + ok = emqx_logger_formatter:check_config(#{max_size => unlimited}), + ?cfgerr({max_size,bad}) = + emqx_logger_formatter:check_config(#{max_size => bad}), + + ok = + emqx_logger_formatter:check_config(#{report_cb => fun(_,_) -> "" end}), + ?cfgerr({report_cb,F}) = + emqx_logger_formatter:check_config(#{report_cb => F=fun(_,_,_) -> {"",[]} end}), + ?cfgerr({report_cb,bad}) = + emqx_logger_formatter:check_config(#{report_cb => bad}), + + Ts = [[key], + [[key1,key2]], + [{key,[key],[]}], + [{[key1,key2],[[key1,key2]],["noexist"]}], + ["string"]], + [begin + ct:log("check template: ~p",[T]), + ok = emqx_logger_formatter:check_config(#{template => T}) + end + || T <- Ts], + + ETs = [bad, + [{key,bad}], + [{key,[key],bad}], + [{key,[key],"bad"}], + "bad", + [[key,$a,$b,$c]], + [[$a,$b,$c,key]]], + [begin + ct:log("check template: ~p",[T]), + {error,{invalid_formatter_template,emqx_logger_formatter,T}} = + emqx_logger_formatter:check_config(#{template => T}) + end + || T <- ETs], + ok. + +%% Test that formatter config can be changed, and that the default +%% template is updated accordingly +update_config(_Config) -> + {error,{not_found,?MODULE}} = logger:update_formatter_config(?MODULE,#{}), + + logger:add_handler_filter(default,silence,{fun(_,_) -> stop end,ok}), + ok = logger:add_handler(?MODULE,?MODULE,#{formatter => {emqx_logger_formatter, #{chars_limit => unlimited}}, + config => #{type => standard_io}}), + D = lists:seq(1,1000), + logger:notice("~p~n",[D]), + {Lines1,C1} = check_log(), + ct:log("lines1: ~p", [Lines1]), + ct:log("c1: ~p",[C1]), + [Line1 | _] = Lines1, + [_Date, WithOutDate1] = string:split(Line1," "), + [_, "notice: "++D1] = string:split(WithOutDate1," "), + ?assert(length(D1)<1000), + ?assertMatch(#{chars_limit := unlimited}, C1), + + error_logger:error_msg("~p",[D]), + {Lines5,C5} = check_log(), + ct:log("Lines5: ~p", [Lines5]), + ct:log("c5: ~p",[C5]), + [Line5 | _] = Lines5, + [_Date, WithOutDate5] = string:split(Line5," "), + [_, "error: "++_D5] = string:split(WithOutDate5," "), + + ?assertMatch({error,{invalid_formatter_config,bad}}, + logger:update_formatter_config(?MODULE,bad)), + ?assertMatch({error,{invalid_formatter_config,emqx_logger_formatter,{depth,bad}}}, + logger:update_formatter_config(?MODULE,depth,bad)), + + ok. + +update_config(cleanup,_Config) -> + _ = logger:remove_handler(?MODULE), + _ = logger:remove_handler_filter(default,silence), + ok. + +%%%----------------------------------------------------------------- +%%% Internal +format(Level,Msg,Meta,Config) -> + format(#{level=>Level,msg=>Msg,meta=>add_time(Meta)},Config). + +format(Log,Config) -> + lists:flatten(emqx_logger_formatter:format(Log,Config)). + +default_time_format(SysTime) when is_integer(SysTime) -> + Ms = SysTime rem 1000000 div 1000, + {Date, _Time = {H, Mi, S}} = calendar:system_time_to_local_time(SysTime, microsecond), + default_time_format({Date, {H, Mi, S, Ms}}); +default_time_format({{Y, M, D}, {H, Mi, S, Ms}}) -> + io_lib:format("~b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b.~3..0b", [Y, M, D, H, Mi, S, Ms]); +default_time_format({{Y, M, D}, {H, Mi, S}}) -> + io_lib:format("~b-~2..0b-~2..0b ~2..0b:~2..0b:~2..0b", [Y, M, D, H, Mi, S]). + +integer(Str) -> + is_integer(list_to_integer(Str)). +integer(Str,Max) -> + integer(Str,0,Max). +integer(Str,Min,Max) -> + Int = list_to_integer(Str), + Int >= Min andalso Int = + try Fun() catch C:R:S -> {C,R,hd(S)} end. + +timestamp() -> + erlang:system_time(microsecond). + +%% necessary? +add_time(#{time:=_}=Meta) -> + Meta; +add_time(Meta) -> + Meta#{time=>timestamp()}. + +%%%----------------------------------------------------------------- +%%% handler callback +log(Log,#{formatter:={M,C}}) -> + put(log,{M:format(Log,C),C}), + ok. + +check_log() -> + {S,C} = erase(log), + {string:lexemes(S,"\n"),C}. From 1dfbe8e166cb9dbe48919c84daf5ab03280bc1a5 Mon Sep 17 00:00:00 2001 From: turtled Date: Tue, 22 Oct 2019 09:11:42 +0800 Subject: [PATCH 08/40] Fix WS badmatch --- src/emqx_ws_connection.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 99226ec6e..4fb39e16e 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -195,7 +195,7 @@ websocket_handle({binary, Data}, State = #state{channel = Channel}) -> ?LOG(debug, "RECV ~p", [Data]), Oct = iolist_size(Data), ok = inc_recv_stats(1, Oct), - {ok, NChannel} = emqx_channel:recvd(Oct, Channel), + NChannel = emqx_channel:recvd(Oct, Channel), parse_incoming(Data, State#state{channel = NChannel}); %% Pings should be replied with pongs, cowboy does it automatically From 9aef5d60e077c7de3b5e21fb7fd289d928cb68b2 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Wed, 23 Oct 2019 10:26:25 +0800 Subject: [PATCH 09/40] Fix testcase for logger_formatter --- test/emqx_logger_formatter_SUITE.erl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/emqx_logger_formatter_SUITE.erl b/test/emqx_logger_formatter_SUITE.erl index a135c7bc9..6699ae68c 100644 --- a/test/emqx_logger_formatter_SUITE.erl +++ b/test/emqx_logger_formatter_SUITE.erl @@ -567,6 +567,8 @@ check_config(_Config) -> %% Test that formatter config can be changed, and that the default %% template is updated accordingly update_config(_Config) -> + #{level := OldLevel} = logger:get_primary_config(), + logger:set_primary_config(level, debug), {error,{not_found,?MODULE}} = logger:update_formatter_config(?MODULE,#{}), logger:add_handler_filter(default,silence,{fun(_,_) -> stop end,ok}), @@ -596,6 +598,7 @@ update_config(_Config) -> ?assertMatch({error,{invalid_formatter_config,emqx_logger_formatter,{depth,bad}}}, logger:update_formatter_config(?MODULE,depth,bad)), + logger:set_primary_config(level, OldLevel), ok. update_config(cleanup,_Config) -> From 7be110cdef8aa3c017afe4c8fe2cba0b8e18ed17 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Wed, 23 Oct 2019 16:35:15 +0800 Subject: [PATCH 10/40] Add more test cases --- test/emqx_SUITE.erl | 54 ++++++++++++++++++++ test/emqx_access_control_SUITE.erl | 40 +++++++++++++++ test/emqx_access_rule_SUITE.erl | 37 ++++++++++++++ test/emqx_acl_cache_SUITE.erl | 67 ++++++++++++++++++++++++ test/emqx_base62_SUITE.erl | 7 +++ test/emqx_batch_SUITE.erl | 16 ++++++ test/emqx_broker_SUITE.erl | 59 +++++++++++++++++++-- test/emqx_broker_helper_SUITE.erl | 70 +++++++++++++++++++++++++ test/emqx_cm_SUITE.erl | 24 +++++++++ test/emqx_cm_locker_SUITE.erl | 43 ++++++++++++++++ test/emqx_cm_registry_SUITE.erl | 64 +++++++++++++++++++++++ test/emqx_flapping_SUITE.erl | 8 +++ test/emqx_guid_SUITE.erl | 22 ++++++++ test/emqx_hooks_SUITE.erl | 19 +++++++ test/emqx_inflight_SUITE.erl | 13 +++++ test/emqx_keepalive_SUITE.erl | 7 +++ test/emqx_listeners_SUITE.erl | 19 +++++++ test/emqx_logger_SUITE.erl | 76 ++++++++++++++++++++++++++++ test/emqx_message_SUITE.erl | 21 +++++++- test/emqx_metrics_SUITE.erl | 15 ++++++ test/emqx_mod_acl_internal_SUITE.erl | 46 +++++++++++++++++ test/emqx_mod_presence_SUITE.erl | 44 ++++++++++++++++ test/emqx_mod_rewrite_SUITE.erl | 46 +++++++++++++++++ test/emqx_mod_subscription_SUITE.erl | 40 +++++++++++++++ test/emqx_modules_SUITE.erl | 7 +++ test/emqx_mqtt_caps_SUITE.erl | 8 +++ test/emqx_mqtt_props_SUITE.erl | 9 ++++ test/emqx_mqueue_SUITE.erl | 19 +++++++ test/emqx_oom_SUITE.erl | 4 ++ test/emqx_os_mon_SUITE.erl | 37 ++++++++++++++ test/emqx_packet_SUITE.erl | 4 ++ test/emqx_pd_SUITE.erl | 6 +++ test/emqx_plugins_SUITE.erl | 18 +++++++ test/emqx_pmon_SUITE.erl | 11 ++++ test/emqx_pool_SUITE.erl | 3 ++ test/emqx_pqueue_SUITE.erl | 43 ++++++++++++++++ test/emqx_reason_codes_SUITE.erl | 19 +++++++ test/emqx_router_SUITE.erl | 18 +++++++ test/emqx_router_helper_SUITE.erl | 41 +++++++++++++++ test/emqx_rpc_SUITE.erl | 11 ++++ test/emqx_sequence_SUITE.erl | 17 +++++++ test/emqx_shared_sub_SUITE.erl | 25 +++++++++ test/emqx_stats_SUITE.erl | 13 +++++ test/emqx_sys_SUITE.erl | 22 ++++++++ test/emqx_topic_SUITE.erl | 3 ++ test/emqx_tracer_SUITE.erl | 17 +++++++ test/emqx_vm_mon_SUITE.erl | 12 +++++ 47 files changed, 1219 insertions(+), 5 deletions(-) create mode 100644 test/emqx_access_control_SUITE.erl create mode 100644 test/emqx_access_rule_SUITE.erl create mode 100644 test/emqx_acl_cache_SUITE.erl create mode 100644 test/emqx_broker_helper_SUITE.erl create mode 100644 test/emqx_cm_locker_SUITE.erl create mode 100644 test/emqx_cm_registry_SUITE.erl create mode 100644 test/emqx_logger_SUITE.erl create mode 100644 test/emqx_mod_acl_internal_SUITE.erl create mode 100644 test/emqx_mod_presence_SUITE.erl create mode 100644 test/emqx_mod_rewrite_SUITE.erl create mode 100644 test/emqx_mod_subscription_SUITE.erl create mode 100644 test/emqx_router_helper_SUITE.erl diff --git a/test/emqx_SUITE.erl b/test/emqx_SUITE.erl index 0a6ef4d0a..7fcb51332 100644 --- a/test/emqx_SUITE.erl +++ b/test/emqx_SUITE.erl @@ -25,6 +25,7 @@ all() -> emqx_ct:all(?MODULE). + init_per_suite(Config) -> emqx_ct_helpers:start_apps([]), Config. @@ -32,6 +33,59 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). + +t_start(_) -> + error('TODO'). + +t_restart(_) -> + error('TODO'). + +t_stop(_) -> + error('TODO'). + +t_is_running(_) -> + error('TODO'). + +t_subscribe(_) -> + error('TODO'). + +t_publish(_) -> + error('TODO'). + +t_unsubscribe(_) -> + error('TODO'). + +t_topics(_) -> + error('TODO'). + +t_subscribers(_) -> + error('TODO'). + +t_subscriptions(_) -> + error('TODO'). + +t_subscribed(_) -> + error('TODO'). + +t_hook(_) -> + error('TODO'). + +t_unhook(_) -> + error('TODO'). + +t_run_hook(_) -> + error('TODO'). + +t_run_fold_hook(_) -> + error('TODO'). + +t_shutdown(_) -> + error('TODO'). + +t_reboot(_) -> + error('TODO'). + + t_get_env(_) -> ?assertEqual(undefined, emqx:get_env(undefined_key)), ?assertEqual(default_value, emqx:get_env(undefined_key, default_value)), diff --git a/test/emqx_access_control_SUITE.erl b/test/emqx_access_control_SUITE.erl new file mode 100644 index 000000000..06483b895 --- /dev/null +++ b/test/emqx_access_control_SUITE.erl @@ -0,0 +1,40 @@ +%%-------------------------------------------------------------------- +%% 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_access_control_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_authenticate(_) -> + error('TODO'). + +t_check_acl(_) -> + error('TODO'). + +t_reload_acl(_) -> + error('TODO'). + diff --git a/test/emqx_access_rule_SUITE.erl b/test/emqx_access_rule_SUITE.erl new file mode 100644 index 000000000..36f887403 --- /dev/null +++ b/test/emqx_access_rule_SUITE.erl @@ -0,0 +1,37 @@ +%%-------------------------------------------------------------------- +%% 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_access_rule_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_compile(_) -> + error('TODO'). + +t_match(_) -> + error('TODO'). + diff --git a/test/emqx_acl_cache_SUITE.erl b/test/emqx_acl_cache_SUITE.erl new file mode 100644 index 000000000..1971612b3 --- /dev/null +++ b/test/emqx_acl_cache_SUITE.erl @@ -0,0 +1,67 @@ +%%-------------------------------------------------------------------- +%% 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_acl_cache_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_cache_k(_) -> + error('TODO'). + +t_cache_v(_) -> + error('TODO'). + +t_cleanup_acl_cache(_) -> + error('TODO'). + +t_get_oldest_key(_) -> + error('TODO'). + +t_get_newest_key(_) -> + error('TODO'). + +t_get_cache_max_size(_) -> + error('TODO'). + +t_get_cache_size(_) -> + error('TODO'). + +t_dump_acl_cache(_) -> + error('TODO'). + +t_empty_acl_cache(_) -> + error('TODO'). + +t_put_acl_cache(_) -> + error('TODO'). + +t_get_acl_cache(_) -> + error('TODO'). + +t_is_enabled(_) -> + error('TODO'). + diff --git a/test/emqx_base62_SUITE.erl b/test/emqx_base62_SUITE.erl index 83d11ae1c..413fe660b 100644 --- a/test/emqx_base62_SUITE.erl +++ b/test/emqx_base62_SUITE.erl @@ -24,6 +24,13 @@ all() -> emqx_ct:all(?MODULE). +t_encode(_) -> + error('TODO'). + +t_decode(_) -> + error('TODO'). + + t_proper_base62(_) -> Opts = [{numtests, 100}, {to_file, user}], ?assert(proper:quickcheck(prop_symmetric(), Opts)), diff --git a/test/emqx_batch_SUITE.erl b/test/emqx_batch_SUITE.erl index e45e89c83..df9f7f778 100644 --- a/test/emqx_batch_SUITE.erl +++ b/test/emqx_batch_SUITE.erl @@ -23,6 +23,22 @@ all() -> emqx_ct:all(?MODULE). + +t_init(_) -> + error('TODO'). + +t_push(_) -> + error('TODO'). + +t_commit(_) -> + error('TODO'). + +t_size(_) -> + error('TODO'). + +t_items(_) -> + error('TODO'). + t_batch_full_commit(_) -> B0 = emqx_batch:init(#{batch_size => 3, linger_ms => 2000, diff --git a/test/emqx_broker_SUITE.erl b/test/emqx_broker_SUITE.erl index 9d6372366..3be56157a 100644 --- a/test/emqx_broker_SUITE.erl +++ b/test/emqx_broker_SUITE.erl @@ -27,10 +27,7 @@ -include("emqx.hrl"). -include("emqx_mqtt.hrl"). -all() -> - [{group, pubsub}, - {group, metrics}, - {group, stats}]. +all() -> emqx_ct:all(?MODULE). groups() -> [{pubsub, [sequence], @@ -159,3 +156,57 @@ set_get_stat(_) -> emqx_stats:setstat('retained.max', 99), ?assertEqual(99, emqx_stats:getstat('retained.max')). + +t_dispatch(_) -> + error('TODO'). + +t_subscriber_down(_) -> + error('TODO'). + +t_get_subopts(_) -> + error('TODO'). + +t_set_subopts(_) -> + error('TODO'). + +t_topics(_) -> + error('TODO'). + +t_stats_fun(_) -> + error('TODO'). + +t_init(_) -> + error('TODO'). + +t_handle_call(_) -> + error('TODO'). + +t_handle_cast(_) -> + error('TODO'). + +t_handle_info(_) -> + error('TODO'). + +t_terminate(_) -> + error('TODO'). + +t_code_change(_) -> + error('TODO'). + +t_safe_publish(_) -> + error('TODO'). + +t_subscribed(_) -> + error('TODO'). + +t_subscriptions(_) -> + error('TODO'). + +t_subscribers(_) -> + error('TODO'). + +t_unsubscribe(_) -> + error('TODO'). + +t_subscribe(_) -> + error('TODO'). diff --git a/test/emqx_broker_helper_SUITE.erl b/test/emqx_broker_helper_SUITE.erl new file mode 100644 index 000000000..e027bb1ef --- /dev/null +++ b/test/emqx_broker_helper_SUITE.erl @@ -0,0 +1,70 @@ +%%-------------------------------------------------------------------- +%% 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_broker_helper_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_start_link(_) -> + error('TODO'). + +t_lookup_subid(_) -> + error('TODO'). + +t_create_seq(_) -> + error('TODO'). + +t_init(_) -> + error('TODO'). + +t_handle_call(_) -> + error('TODO'). + +t_handle_cast(_) -> + error('TODO'). + +t_handle_info(_) -> + error('TODO'). + +t_terminate(_) -> + error('TODO'). + +t_code_change(_) -> + error('TODO'). + +t_lookup_subpid(_) -> + error('TODO'). + +t_reclaim_seq(_) -> + error('TODO'). + +t_get_sub_shard(_) -> + error('TODO'). + +t_register_sub(_) -> + error('TODO'). + diff --git a/test/emqx_cm_SUITE.erl b/test/emqx_cm_SUITE.erl index 1ec016b5f..0e13bd785 100644 --- a/test/emqx_cm_SUITE.erl +++ b/test/emqx_cm_SUITE.erl @@ -88,3 +88,27 @@ t_lock_clientid(_) -> {true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>), {true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>). + +t_unregister_channel(_) -> + error('TODO'). + +t_get_chan_attrs(_) -> + error('TODO'). + +t_get_chan_stats(_) -> + error('TODO'). + +t_lookup_channels(_) -> + error('TODO'). + +t_set_chan_stats(_) -> + error('TODO'). + +t_set_chan_attrs(_) -> + error('TODO'). + +t_register_channel(_) -> + error('TODO'). + +t_stats_fun(_) -> + error('TODO'). diff --git a/test/emqx_cm_locker_SUITE.erl b/test/emqx_cm_locker_SUITE.erl new file mode 100644 index 000000000..937305c23 --- /dev/null +++ b/test/emqx_cm_locker_SUITE.erl @@ -0,0 +1,43 @@ +%%-------------------------------------------------------------------- +%% 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_locker_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_start_link(_) -> + error('TODO'). + +t_trans(_) -> + error('TODO'). + +t_lock(_) -> + error('TODO'). + +t_unlock(_) -> + error('TODO'). + diff --git a/test/emqx_cm_registry_SUITE.erl b/test/emqx_cm_registry_SUITE.erl new file mode 100644 index 000000000..ac0988c7f --- /dev/null +++ b/test/emqx_cm_registry_SUITE.erl @@ -0,0 +1,64 @@ +%%-------------------------------------------------------------------- +%% 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_registry_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_start_link(_) -> + error('TODO'). + +t_init(_) -> + error('TODO'). + +t_handle_call(_) -> + error('TODO'). + +t_handle_cast(_) -> + error('TODO'). + +t_handle_info(_) -> + error('TODO'). + +t_terminate(_) -> + error('TODO'). + +t_code_change(_) -> + error('TODO'). + +t_lookup_channels(_) -> + error('TODO'). + +t_is_enabled(_) -> + error('TODO'). + +t_unregister_channel(_) -> + error('TODO'). + +t_register_channel(_) -> + error('TODO'). + diff --git a/test/emqx_flapping_SUITE.erl b/test/emqx_flapping_SUITE.erl index a9234c541..430edcc3a 100644 --- a/test/emqx_flapping_SUITE.erl +++ b/test/emqx_flapping_SUITE.erl @@ -38,6 +38,14 @@ set_special_configs(_App) -> ok. end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]), ok. + + +t_check(_) -> + error('TODO'). + +t_detect(_) -> + error('TODO'). + t_detect_check(_) -> ClientInfo = #{zone => external, diff --git a/test/emqx_guid_SUITE.erl b/test/emqx_guid_SUITE.erl index 004661b72..56fcd8aaf 100644 --- a/test/emqx_guid_SUITE.erl +++ b/test/emqx_guid_SUITE.erl @@ -39,4 +39,26 @@ t_guid_hexstr(_) -> t_guid_base62(_) -> Guid = emqx_guid:gen(), ?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))). + +t_new(_) -> + error('TODO'). + +t_timestamp(_) -> + error('TODO'). + +t_to_hexstr(_) -> + error('TODO'). + +t_from_hexstr(_) -> + error('TODO'). + +t_from_base62(_) -> + error('TODO'). + +t_to_base62(_) -> + error('TODO'). + +t_gen(_) -> + error('TODO'). + diff --git a/test/emqx_hooks_SUITE.erl b/test/emqx_hooks_SUITE.erl index 102a66161..e5d04bd6d 100644 --- a/test/emqx_hooks_SUITE.erl +++ b/test/emqx_hooks_SUITE.erl @@ -23,6 +23,25 @@ all() -> emqx_ct:all(?MODULE). + + +t_lookup(_) -> + error('TODO'). + + +t_run_fold(_) -> + error('TODO'). + +t_run(_) -> + error('TODO'). + +t_del(_) -> + error('TODO'). + +t_add(_) -> + error('TODO'). + + t_add_del_hook(_) -> {ok, _} = emqx_hooks:start_link(), ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []), diff --git a/test/emqx_inflight_SUITE.erl b/test/emqx_inflight_SUITE.erl index 4d7065486..f441b0f81 100644 --- a/test/emqx_inflight_SUITE.erl +++ b/test/emqx_inflight_SUITE.erl @@ -24,6 +24,10 @@ all() -> emqx_ct:all(?MODULE). + +t_new(_) -> + error('TODO'). + t_contain(_) -> Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()), ?assert(emqx_inflight:contain(k, Inflight)), @@ -90,3 +94,12 @@ t_window(_) -> a, 1, emqx_inflight:new(2))), ?assertEqual([a, b], emqx_inflight:window(Inflight)). +t_to_list(_) -> + error('TODO'). + +t_size(_) -> + error('TODO'). + +t_max_size(_) -> + error('TODO'). + diff --git a/test/emqx_keepalive_SUITE.erl b/test/emqx_keepalive_SUITE.erl index 0bdc79f60..58c3d0af0 100644 --- a/test/emqx_keepalive_SUITE.erl +++ b/test/emqx_keepalive_SUITE.erl @@ -23,6 +23,13 @@ all() -> emqx_ct:all(?MODULE). + +t_init(_) -> + error('TODO'). + +t_info(_) -> + error('TODO'). + t_check(_) -> Keepalive = emqx_keepalive:init(60), ?assertEqual(60, emqx_keepalive:info(interval, Keepalive)), diff --git a/test/emqx_listeners_SUITE.erl b/test/emqx_listeners_SUITE.erl index 1cba68b74..a03485017 100644 --- a/test/emqx_listeners_SUITE.erl +++ b/test/emqx_listeners_SUITE.erl @@ -89,4 +89,23 @@ get_base_dir(Module) -> get_base_dir() -> get_base_dir(?MODULE). + + +t_start_listener(_) -> + error('TODO'). + +t_restart(_) -> + error('TODO'). + +t_restart_listener(_) -> + error('TODO'). + +t_stop_listener(_) -> + error('TODO'). + +t_stop(_) -> + error('TODO'). + +t_start(_) -> + error('TODO'). diff --git a/test/emqx_logger_SUITE.erl b/test/emqx_logger_SUITE.erl new file mode 100644 index 000000000..16f20dc3c --- /dev/null +++ b/test/emqx_logger_SUITE.erl @@ -0,0 +1,76 @@ +%%-------------------------------------------------------------------- +%% 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_logger_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_debug(_) -> + error('TODO'). + +t_info(_) -> + error('TODO'). + +t_warning(_) -> + error('TODO'). + +t_error(_) -> + error('TODO'). + +t_critical(_) -> + error('TODO'). + +t_set_proc_metadata(_) -> + error('TODO'). + +t_get_primary_log_level(_) -> + error('TODO'). + +t_set_primary_log_level(_) -> + error('TODO'). + +t_get_log_handlers(_) -> + error('TODO'). + +t_get_log_handler(_) -> + error('TODO'). + +t_set_log_handler_level(_) -> + error('TODO'). + +t_set_log_level(_) -> + error('TODO'). + +t_parse_transform(_) -> + error('TODO'). + +t_set_metadata_peername(_) -> + error('TODO'). + +t_set_metadata_clientid(_) -> + error('TODO'). + diff --git a/test/emqx_message_SUITE.erl b/test/emqx_message_SUITE.erl index 0c1cad6c7..160b15aa1 100644 --- a/test/emqx_message_SUITE.erl +++ b/test/emqx_message_SUITE.erl @@ -91,6 +91,9 @@ t_undefined_headers(_) -> ?assertEqual(1, emqx_message:get_header(a, Msg1)), Msg2 = emqx_message:set_header(c, 3, Msg), ?assertEqual(3, emqx_message:get_header(c, Msg2)). + +t_remove_header(_) -> + error('TODO'). t_format(_) -> Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>), @@ -102,7 +105,7 @@ t_format(_) -> }, io:format("~s~n", [emqx_message:format(Msg1)]). -t_expired(_) -> +t_is_expired(_) -> Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>), ?assertNot(emqx_message:is_expired(Msg)), Msg1 = emqx_message:set_headers(#{'Message-Expiry-Interval' => 1}, Msg), @@ -115,6 +118,10 @@ t_expired(_) -> Msg2 = emqx_message:update_expiry(Msg1), ?assertEqual(1, emqx_message:get_header('Message-Expiry-Interval', Msg2)). + +t_to_list(_) -> + error('TODO'). + t_to_packet(_) -> Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, qos = ?QOS_0, @@ -139,4 +146,16 @@ t_to_map(_) -> {timestamp, emqx_message:timestamp(Msg)}], ?assertEqual(List, emqx_message:to_list(Msg)), ?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)). + +t_update_expiry(_) -> + error('TODO'). + +t_set_header(_) -> + error('TODO'). + +t_set_flag(_) -> + error('TODO'). + +t_set_headers(_) -> + error('TODO'). diff --git a/test/emqx_metrics_SUITE.erl b/test/emqx_metrics_SUITE.erl index 7e547259e..2b704d085 100644 --- a/test/emqx_metrics_SUITE.erl +++ b/test/emqx_metrics_SUITE.erl @@ -24,6 +24,21 @@ all() -> emqx_ct:all(?MODULE). +t_val(_) -> + error('TODO'). + +t_dec(_) -> + error('TODO'). + +t_set(_) -> + error('TODO'). + +t_commit(_) -> + error('TODO'). + +t_inc(_) -> + error('TODO'). + t_new(_) -> with_metrics_server( fun() -> diff --git a/test/emqx_mod_acl_internal_SUITE.erl b/test/emqx_mod_acl_internal_SUITE.erl new file mode 100644 index 000000000..0cd9c1cd8 --- /dev/null +++ b/test/emqx_mod_acl_internal_SUITE.erl @@ -0,0 +1,46 @@ +%%-------------------------------------------------------------------- +%% 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_acl_internal_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_load(_) -> + error('TODO'). + +t_unload(_) -> + error('TODO'). + +t_all_rules(_) -> + error('TODO'). + +t_check_acl(_) -> + error('TODO'). + +t_reload_acl(_) -> + error('TODO'). + diff --git a/test/emqx_mod_presence_SUITE.erl b/test/emqx_mod_presence_SUITE.erl new file mode 100644 index 000000000..a5fba303e --- /dev/null +++ b/test/emqx_mod_presence_SUITE.erl @@ -0,0 +1,44 @@ +%%-------------------------------------------------------------------- +%% 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_presence_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_load(_) -> + error('TODO'). + +t_unload(_) -> + error('TODO'). + +t_on_client_connected(_) -> + error('TODO'). + +t_on_client_disconnected(_) -> + error('TODO'). + + diff --git a/test/emqx_mod_rewrite_SUITE.erl b/test/emqx_mod_rewrite_SUITE.erl new file mode 100644 index 000000000..dcc0c6a6b --- /dev/null +++ b/test/emqx_mod_rewrite_SUITE.erl @@ -0,0 +1,46 @@ +%%-------------------------------------------------------------------- +%% 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_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_load(_) -> + error('TODO'). + +t_rewrite_subscribe(_) -> + error('TODO'). + +t_rewrite_unsubscribe(_) -> + error('TODO'). + +t_rewrite_publish(_) -> + error('TODO'). + +t_unload(_) -> + error('TODO'). + diff --git a/test/emqx_mod_subscription_SUITE.erl b/test/emqx_mod_subscription_SUITE.erl new file mode 100644 index 000000000..026bc976a --- /dev/null +++ b/test/emqx_mod_subscription_SUITE.erl @@ -0,0 +1,40 @@ +%%-------------------------------------------------------------------- +%% 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_subscription_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_load(_) -> + error('TODO'). + +t_on_client_connected(_) -> + error('TODO'). + +t_unload(_) -> + error('TODO'). + diff --git a/test/emqx_modules_SUITE.erl b/test/emqx_modules_SUITE.erl index 437a21aec..886a8d101 100644 --- a/test/emqx_modules_SUITE.erl +++ b/test/emqx_modules_SUITE.erl @@ -52,6 +52,13 @@ end_per_suite(_Config) -> %% Test cases %%-------------------------------------------------------------------- +t_unload(_) -> + error('TODO'). + +t_load(_) -> + error('TODO'). + + %% Test case for emqx_mod_presence t_mod_presence(_) -> ok = emqx_mod_presence:load([{qos, ?QOS_1}]), diff --git a/test/emqx_mqtt_caps_SUITE.erl b/test/emqx_mqtt_caps_SUITE.erl index 961229ed0..432525bd1 100644 --- a/test/emqx_mqtt_caps_SUITE.erl +++ b/test/emqx_mqtt_caps_SUITE.erl @@ -24,6 +24,14 @@ all() -> emqx_ct:all(?MODULE). + +t_get_caps(_) -> + error('TODO'). + +t_default(_) -> + error('TODO'). + + t_check_pub(_) -> PubCaps = #{max_qos_allowed => ?QOS_1, retain_available => false diff --git a/test/emqx_mqtt_props_SUITE.erl b/test/emqx_mqtt_props_SUITE.erl index 17ad975b6..ef4876671 100644 --- a/test/emqx_mqtt_props_SUITE.erl +++ b/test/emqx_mqtt_props_SUITE.erl @@ -78,3 +78,12 @@ t_validate_value(_) -> foreach_prop(Fun) -> lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())). + +t_all(_) -> + error('TODO'). + +t_set(_) -> + error('TODO'). + +t_get(_) -> + error('TODO'). \ No newline at end of file diff --git a/test/emqx_mqueue_SUITE.erl b/test/emqx_mqueue_SUITE.erl index cd15b1b8e..c33f415a8 100644 --- a/test/emqx_mqueue_SUITE.erl +++ b/test/emqx_mqueue_SUITE.erl @@ -28,6 +28,25 @@ all() -> emqx_ct:all(?MODULE). + +t_init(_) -> + error('TODO'). + +t_is_empty(_) -> + error('TODO'). + +t_len(_) -> + error('TODO'). + +t_max_len(_) -> + error('TODO'). + +t_dropped(_) -> + error('TODO'). + +t_stats(_) -> + error('TODO'). + t_in(_) -> Opts = #{max_len => 5, store_qos0 => true}, Q = ?Q:init(Opts), diff --git a/test/emqx_oom_SUITE.erl b/test/emqx_oom_SUITE.erl index c5a7edc1e..c7cae0c36 100644 --- a/test/emqx_oom_SUITE.erl +++ b/test/emqx_oom_SUITE.erl @@ -41,4 +41,8 @@ t_check(_) -> ?assertEqual(ok, emqx_oom:check(Oom)), [self() ! {msg, I} || I <- lists:seq(1, 6)], ?assertEqual({shutdown, message_queue_too_long}, emqx_oom:check(Oom)). + + +t_info(_) -> + error('TODO'). diff --git a/test/emqx_os_mon_SUITE.erl b/test/emqx_os_mon_SUITE.erl index 7d816000f..17a5fc19c 100644 --- a/test/emqx_os_mon_SUITE.erl +++ b/test/emqx_os_mon_SUITE.erl @@ -29,6 +29,43 @@ init_per_suite(Config) -> end_per_suite(_Config) -> application:stop(os_mon). + + +t_get_cpu_check_interval(_) -> + error('TODO'). + +t_set_cpu_check_interval(_) -> + error('TODO'). + +t_get_cpu_high_watermark(_) -> + error('TODO'). + +t_set_cpu_high_watermark(_) -> + error('TODO'). + +t_get_cpu_low_watermark(_) -> + error('TODO'). + +t_set_cpu_low_watermark(_) -> + error('TODO'). + +t_get_mem_check_interval(_) -> + error('TODO'). + +t_set_mem_check_interval(_) -> + error('TODO'). + +t_get_sysmem_high_watermark(_) -> + error('TODO'). + +t_set_sysmem_high_watermark(_) -> + error('TODO'). + +t_get_procmem_high_watermark(_) -> + error('TODO'). + +t_set_procmem_high_watermark(_) -> + error('TODO'). t_api(_) -> gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}), diff --git a/test/emqx_packet_SUITE.erl b/test/emqx_packet_SUITE.erl index 5b9845231..bcc4439b1 100644 --- a/test/emqx_packet_SUITE.erl +++ b/test/emqx_packet_SUITE.erl @@ -172,6 +172,10 @@ t_will_msg(_) -> Msg = emqx_packet:will_msg(Pkt), ?assertEqual(<<"clientid">>, Msg#message.from), ?assertEqual(<<"topic">>, Msg#message.topic). + +t_to_message(_) -> + error('TODO'). + t_format(_) -> io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]), diff --git a/test/emqx_pd_SUITE.erl b/test/emqx_pd_SUITE.erl index 7c08d7f18..5e86b42f4 100644 --- a/test/emqx_pd_SUITE.erl +++ b/test/emqx_pd_SUITE.erl @@ -23,6 +23,12 @@ all() -> emqx_ct:all(?MODULE). +t_get_counter(_) -> + error('TODO'). + +t_reset_counter(_) -> + error('TODO'). + t_update_counter(_) -> ?assertEqual(undefined, emqx_pd:update_counter(bytes, 1)), ?assertEqual(1, emqx_pd:update_counter(bytes, 1)), diff --git a/test/emqx_plugins_SUITE.erl b/test/emqx_plugins_SUITE.erl index 793e506a3..c4a962689 100644 --- a/test/emqx_plugins_SUITE.erl +++ b/test/emqx_plugins_SUITE.erl @@ -23,6 +23,7 @@ all() -> emqx_ct:all(?MODULE). + init_per_suite(Config) -> %% Compile extra plugin code @@ -41,6 +42,23 @@ init_per_suite(Config) -> emqx_ct_helpers:start_apps([], fun set_sepecial_cfg/1), Config. + + +t_load_expand_plugin(_) -> + error('TODO'). + +t_list(_) -> + error('TODO'). + +t_find_plugin(_) -> + error('TODO'). + +t_unload(_) -> + error('TODO'). + + +t_init(_) -> + error('TODO'). set_sepecial_cfg(_) -> ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)), diff --git a/test/emqx_pmon_SUITE.erl b/test/emqx_pmon_SUITE.erl index 17c56729e..2fcfa4be9 100644 --- a/test/emqx_pmon_SUITE.erl +++ b/test/emqx_pmon_SUITE.erl @@ -23,6 +23,12 @@ all() -> emqx_ct:all(?MODULE). +t_new(_) -> + error('TODO'). + +t_count(_) -> + error('TODO'). + t_monitor(_) -> PMon = emqx_pmon:new(), PMon1 = emqx_pmon:monitor(self(), PMon), @@ -31,6 +37,9 @@ t_monitor(_) -> PMon2 = emqx_pmon:demonitor(self(), PMon2), ?assertEqual(0, emqx_pmon:count(PMon2)). +t_demonitor(_) -> + error('TODO'). + t_find(_) -> PMon = emqx_pmon:new(), PMon1 = emqx_pmon:monitor(self(), val, PMon), @@ -51,3 +60,5 @@ t_erase(_) -> ?assertEqual([{self(), val}], Items), ?assertEqual(0, emqx_pmon:count(PMon3)). +t_erase_all(_) -> + error('TODO'). diff --git a/test/emqx_pool_SUITE.erl b/test/emqx_pool_SUITE.erl index 5aca5ca3f..06d0f2917 100644 --- a/test/emqx_pool_SUITE.erl +++ b/test/emqx_pool_SUITE.erl @@ -86,3 +86,6 @@ t_unexpected(_) -> test_mfa() -> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). + +t_async_submit(_) -> + error('TODO'). diff --git a/test/emqx_pqueue_SUITE.erl b/test/emqx_pqueue_SUITE.erl index 3960f7e26..377172b18 100644 --- a/test/emqx_pqueue_SUITE.erl +++ b/test/emqx_pqueue_SUITE.erl @@ -26,6 +26,49 @@ all() -> emqx_ct:all(?SUITE). + +t_is_queue(_) -> + error('TODO'). + +t_is_empty(_) -> + error('TODO'). + +t_to_list(_) -> + error('TODO'). + +t_from_list(_) -> + error('TODO'). + +t_in(_) -> + error('TODO'). + +t_out_p(_) -> + error('TODO'). + +t_join(_) -> + error('TODO'). + +t_filter(_) -> + error('TODO'). + +t_fold(_) -> + error('TODO'). + +t_highest(_) -> + error('TODO'). + +t_out(_) -> + error('TODO'). + +t_len(_) -> + error('TODO'). + +t_plen(_) -> + error('TODO'). + +t_new(_) -> + error('TODO'). + t_priority_queue_plen(_) -> Q = ?PQ:new(), 0 = ?PQ:plen(0, Q), diff --git a/test/emqx_reason_codes_SUITE.erl b/test/emqx_reason_codes_SUITE.erl index 82acb8091..b2db5fe42 100644 --- a/test/emqx_reason_codes_SUITE.erl +++ b/test/emqx_reason_codes_SUITE.erl @@ -25,6 +25,25 @@ all() -> emqx_ct:all(?MODULE). + +t_name(_) -> + error('TODO'). + +t_text(_) -> + error('TODO'). + +t_mqtt_frame_error(_) -> + error('TODO'). + +t_connack_error(_) -> + error('TODO'). + +t_compat(_) -> + error('TODO'). + +t_formalized(_) -> + error('TODO'). + t_prop_name_text(_) -> ?assert(proper:quickcheck(prop_name_text(), prop_name_text(opts))). diff --git a/test/emqx_router_SUITE.erl b/test/emqx_router_SUITE.erl index 511b62c8b..46d669d9c 100644 --- a/test/emqx_router_SUITE.erl +++ b/test/emqx_router_SUITE.erl @@ -45,6 +45,24 @@ t_mnesia(_) -> %% for coverage ok = emqx_router:mnesia(copy). +t_add_route(_) -> + error('TODO'). + +t_do_add_route(_) -> + error('TODO'). + +t_lookup_routes(_) -> + error('TODO'). + +t_delete_route(_) -> + error('TODO'). + +t_do_delete_route(_) -> + error('TODO'). + +t_topics(_) -> + error('TODO'). + t_add_delete(_) -> ?R:add_route(<<"a/b/c">>), ?R:add_route(<<"a/b/c">>, node()), diff --git a/test/emqx_router_helper_SUITE.erl b/test/emqx_router_helper_SUITE.erl new file mode 100644 index 000000000..dbf13565b --- /dev/null +++ b/test/emqx_router_helper_SUITE.erl @@ -0,0 +1,41 @@ +%%-------------------------------------------------------------------- +%% 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_router_helper_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-include_lib("eunit/include/eunit.hrl"). + +all() -> emqx_ct:all(?MODULE). + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +t_mnesia(_) -> + error('TODO'). + + +t_monitor(_) -> + error('TODO'). + +t_stats_fun(_) -> + error('TODO'). + diff --git a/test/emqx_rpc_SUITE.erl b/test/emqx_rpc_SUITE.erl index 62b597e1e..99c5c2532 100644 --- a/test/emqx_rpc_SUITE.erl +++ b/test/emqx_rpc_SUITE.erl @@ -24,6 +24,17 @@ all() -> emqx_ct:all(?MODULE). +t_multicall(_) -> + error('TODO'). + +t_cast(_) -> + error('TODO'). + +t_call(_) -> + error('TODO'). + + + t_prop_rpc(_) -> ok = load(), Opts = [{to_file, user}, {numtests, 10}], diff --git a/test/emqx_sequence_SUITE.erl b/test/emqx_sequence_SUITE.erl index a5c49f2f6..4a6ae3463 100644 --- a/test/emqx_sequence_SUITE.erl +++ b/test/emqx_sequence_SUITE.erl @@ -29,6 +29,23 @@ all() -> emqx_ct:all(?MODULE). + +t_currval(_) -> + error('TODO'). + +t_delete(_) -> + error('TODO'). + +t_create(_) -> + error('TODO'). + +t_reclaim(_) -> + error('TODO'). + +t_nextval(_) -> + error('TODO'). + + t_generate(_) -> ok = emqx_sequence:create(seqtab), ?assertEqual(0, currval(seqtab, key)), diff --git a/test/emqx_shared_sub_SUITE.erl b/test/emqx_shared_sub_SUITE.erl index 9fec11171..07a9137b0 100644 --- a/test/emqx_shared_sub_SUITE.erl +++ b/test/emqx_shared_sub_SUITE.erl @@ -38,6 +38,21 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). + + t_is_ack_required(_) -> + error('TODO'). + +t_maybe_nack_dropped(_) -> + error('TODO'). + +t_nack_no_connection(_) -> + error('TODO'). + +t_maybe_ack(_) -> + error('TODO'). + +t_subscribers(_) -> + error('TODO'). t_random_basic(_) -> ok = ensure_config(random), @@ -223,6 +238,16 @@ last_message(ExpectedPayload, Pids) -> after 100 -> <<"not yet?">> end. + +t_dispatch(_) -> + error('TODO'). + +t_unsubscribe(_) -> + error('TODO'). + +t_subscribe(_) -> + error('TODO'). + %%-------------------------------------------------------------------- %% help functions diff --git a/test/emqx_stats_SUITE.erl b/test/emqx_stats_SUITE.erl index b2c0b3524..fcc7b4707 100644 --- a/test/emqx_stats_SUITE.erl +++ b/test/emqx_stats_SUITE.erl @@ -23,6 +23,19 @@ all() -> emqx_ct:all(?MODULE). + +t_statsfun(_) -> + error('TODO'). + +t_getstats(_) -> + error('TODO'). + +t_getstat(_) -> + error('TODO'). + +t_setstat(_) -> + error('TODO'). + t_get_state(_) -> with_proc(fun() -> SetConnsCount = emqx_stats:statsfun('connections.count'), diff --git a/test/emqx_sys_SUITE.erl b/test/emqx_sys_SUITE.erl index 26ec26fc2..d9942fc07 100644 --- a/test/emqx_sys_SUITE.erl +++ b/test/emqx_sys_SUITE.erl @@ -42,6 +42,28 @@ end_per_suite(_Config) -> application:unload(emqx), ok = emqx_logger:set_log_level(error), ok. + + t_version(_) -> + error('TODO'). + +t_sysdescr(_) -> + error('TODO'). + +t_uptime(_) -> + error('TODO'). + +t_datetime(_) -> + error('TODO'). + +t_sys_interval(_) -> + error('TODO'). + +t_sys_heatbeat_interval(_) -> + error('TODO'). + +t_info(_) -> + error('TODO'). + t_prop_sys(_) -> Opts = [{numtests, 100}, {to_file, user}], diff --git a/test/emqx_topic_SUITE.erl b/test/emqx_topic_SUITE.erl index b0776aa69..7eb803895 100644 --- a/test/emqx_topic_SUITE.erl +++ b/test/emqx_topic_SUITE.erl @@ -230,3 +230,6 @@ bench(Case, Fun, Args) -> ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w", [Case, Time/?N, Case, (?N * 1000000) div Time]). + +t_match(_) -> + error('TODO'). diff --git a/test/emqx_tracer_SUITE.erl b/test/emqx_tracer_SUITE.erl index ece0f5799..3936c05fe 100644 --- a/test/emqx_tracer_SUITE.erl +++ b/test/emqx_tracer_SUITE.erl @@ -32,6 +32,7 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). + t_start_traces(_Config) -> {ok, T} = emqtt:start_link([{host, "localhost"}, @@ -80,3 +81,19 @@ t_start_traces(_Config) -> emqtt:disconnect(T), emqx_logger:set_log_level(warning). + + +t_start_trace(_) -> + error('TODO'). + +t_stop_trace(_) -> + error('TODO'). + +t_lookup_traces(_) -> + error('TODO'). + + + +t_trace(_) -> + error('TODO'). + diff --git a/test/emqx_vm_mon_SUITE.erl b/test/emqx_vm_mon_SUITE.erl index 438974ade..cb85b1fac 100644 --- a/test/emqx_vm_mon_SUITE.erl +++ b/test/emqx_vm_mon_SUITE.erl @@ -38,6 +38,18 @@ init_per_suite(Config) -> end_per_suite(_Config) -> application:stop(sasl). + + t_get_process_high_watermark(_) -> + error('TODO'). + +t_set_process_high_watermark(_) -> + error('TODO'). + +t_get_process_low_watermark(_) -> + error('TODO'). + +t_set_process_low_watermark(_) -> + error('TODO'). t_api(_) -> meck:new(alarm_handler, [passthrough, no_history]), From c713d619da7aceb9bc13a2d6094acb509ee979c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=A5=87=E6=80=AA?= Date: Thu, 24 Oct 2019 15:41:26 +0800 Subject: [PATCH 11/40] Add more test cases (#2992) * Add more test cases for emqx_stats and emqx_os_mon Fix test case error for emqx_ws_connection * Add more test cases for emqx_sys_mon * Update erlang otp to 22.1 for travis ci * Delete readable=false for make ct * Add unset_all_env for emqx_zone and update test cases --- .travis.yml | 2 +- Makefile | 2 +- src/emqx_misc.erl | 2 +- src/emqx_zone.erl | 6 +++++ test/emqx_os_mon_SUITE.erl | 33 +++++++++++++++++++++++++++ test/emqx_stats_SUITE.erl | 13 +++++++++++ test/emqx_sys_mon_SUITE.erl | 38 ++++++++++++++++++++++++++++--- test/emqx_ws_connection_SUITE.erl | 2 +- test/emqx_zone_SUITE.erl | 1 + 9 files changed, 92 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index abe6f7a6a..90b15d560 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: erlang otp_release: - - 21.3 + - 22.1 before_install: - git clone https://github.com/erlang/rebar3.git; cd rebar3; ./bootstrap; sudo mv rebar3 /usr/local/bin/; cd .. diff --git a/Makefile b/Makefile index 707380095..774aaaba2 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ ct_setup: .PHONY: ct ct: ct_setup - @rebar3 ct -v --readable=false --name $(CT_NODE_NAME) --suite=$(shell echo $(foreach var,$(CT_SUITES),test/$(var)_SUITE) | tr ' ' ',') + @rebar3 ct -v --name $(CT_NODE_NAME) --suite=$(shell echo $(foreach var,$(CT_SUITES),test/$(var)_SUITE) | tr ' ' ',') ## Run one single CT with rebar3 ## e.g. make ct-one-suite suite=emqx_bridge diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index 359bb045f..e5170c6bc 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -101,7 +101,7 @@ start_timer(Interval, Msg) -> -spec(start_timer(integer(), pid() | atom(), term()) -> reference()). start_timer(Interval, Dest, Msg) -> - erlang:start_timer(Interval, Dest, Msg). + erlang:start_timer(erlang:ceil(Interval), Dest, Msg). -spec(cancel_timer(maybe(reference())) -> ok). cancel_timer(Timer) when is_reference(Timer) -> diff --git a/src/emqx_zone.erl b/src/emqx_zone.erl index 90d56eb0e..a16e2da75 100644 --- a/src/emqx_zone.erl +++ b/src/emqx_zone.erl @@ -50,6 +50,7 @@ , get_env/3 , set_env/3 , unset_env/2 + , unset_all_env/0 ]). -export([force_reload/0]). @@ -175,6 +176,11 @@ set_env(Zone, Key, Val) -> unset_env(Zone, Key) -> persistent_term:erase(?KEY(Zone, Key)). +-spec(unset_all_env() -> ok). +unset_all_env() -> + [unset_env(Zone, Key) || {?KEY(Zone, Key), _Val} <- persistent_term:get()], + ok. + -spec(force_reload() -> ok). force_reload() -> gen_server:call(?SERVER, force_reload). diff --git a/test/emqx_os_mon_SUITE.erl b/test/emqx_os_mon_SUITE.erl index 17a5fc19c..63ba421f6 100644 --- a/test/emqx_os_mon_SUITE.erl +++ b/test/emqx_os_mon_SUITE.erl @@ -84,11 +84,44 @@ t_api(_) -> % timer:sleep(2000), % ?assertEqual(true, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())), + emqx_os_mon:set_cpu_check_interval(0.05), emqx_os_mon:set_cpu_high_watermark(0.8), emqx_os_mon:set_cpu_low_watermark(0.75), + ?assertEqual(0.05, emqx_os_mon:get_cpu_check_interval()), ?assertEqual(0.8, emqx_os_mon:get_cpu_high_watermark()), ?assertEqual(0.75, emqx_os_mon:get_cpu_low_watermark()), % timer:sleep(3000), % ?assertEqual(false, lists:keymember(cpu_high_watermark, 1, alarm_handler:get_alarms())), + ?assertEqual(ignored, gen_server:call(emqx_os_mon, ignored)), + ?assertEqual(ok, gen_server:cast(emqx_os_mon, ignored)), + emqx_os_mon ! ignored, + gen_server:stop(emqx_os_mon), ok. +t_timeout(_) -> + ok = meck:new(emqx_vm), + + ok = meck:expect(emqx_vm, cpu_util, fun() -> 0 end), + {ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}]), + timer:sleep(1500), + gen_server:stop(emqx_os_mon), + + ok = meck:expect(emqx_vm, cpu_util, fun() -> {error, test_case} end), + {ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}]), + timer:sleep(1500), + gen_server:stop(emqx_os_mon), + + ok = meck:expect(emqx_vm, cpu_util, fun() -> 90 end), + {ok, _} = emqx_os_mon:start_link([{cpu_check_interval, 1}, + {cpu_high_watermark, 0.80}, + {cpu_low_watermark, 0.60}]), + timer:sleep(1500), + + emqx_os_mon:set_cpu_high_watermark(1.00), + timer:sleep(1500), + + emqx_os_mon:set_cpu_low_watermark(0.95), + timer:sleep(1500), + + gen_server:stop(emqx_os_mon), + ok = meck:unload(emqx_vm). diff --git a/test/emqx_stats_SUITE.erl b/test/emqx_stats_SUITE.erl index fcc7b4707..c419366c3 100644 --- a/test/emqx_stats_SUITE.erl +++ b/test/emqx_stats_SUITE.erl @@ -23,6 +23,16 @@ all() -> emqx_ct:all(?MODULE). +t_cast_useless_msg(_)-> + emqx_stats:setstat('notExis', 1), + with_proc(fun() -> + emqx_stats ! useless, + ?assertEqual(ok, gen_server:cast(emqx_stats, useless)) + end). + +t_get_error_state(_) -> + Conns = emqx_stats:getstats(), + ?assertEqual([], Conns). t_statsfun(_) -> error('TODO'). @@ -38,6 +48,7 @@ t_setstat(_) -> t_get_state(_) -> with_proc(fun() -> + ?assertEqual(undefined, emqx_stats:getstat('notExist')), SetConnsCount = emqx_stats:statsfun('connections.count'), SetConnsCount(1), ?assertEqual(1, emqx_stats:getstat('connections.count')), @@ -69,6 +80,8 @@ t_update_interval(_) -> UpdFun = fun() -> emqx_stats:setstat('connections.count', 1) end, ok = emqx_stats:update_interval(stats_test, UpdFun), timer:sleep(SleepMs), + ok = emqx_stats:update_interval(stats_test, UpdFun), + timer:sleep(SleepMs), ?assertEqual(1, emqx_stats:getstat('connections.count')) end, TickMs). diff --git a/test/emqx_sys_mon_SUITE.erl b/test/emqx_sys_mon_SUITE.erl index b64181fa3..99b63ae7f 100644 --- a/test/emqx_sys_mon_SUITE.erl +++ b/test/emqx_sys_mon_SUITE.erl @@ -28,6 +28,8 @@ 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(), large_heap, + concat_str("large_heap 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>")}, @@ -41,12 +43,35 @@ all() -> emqx_ct:all(?MODULE). -init_per_suite(Config) -> +init_per_testcase(t_sys_mon, Config) -> emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([]), + emqx_ct_helpers:start_apps([], + fun(emqx) -> + application:set_env(emqx, sysmon, [{busy_dist_port,true}, + {busy_port,false}, + {large_heap,8388608}, + {long_schedule,240}, + {long_gc,0}]), + ok; + (_) -> ok + end), + Config; +init_per_testcase(t_sys_mon2, Config) -> + emqx_ct_helpers:boot_modules(all), + emqx_ct_helpers:start_apps([], + fun(emqx) -> + application:set_env(emqx, sysmon, [{busy_dist_port,false}, + {busy_port,true}, + {large_heap,8388608}, + {long_schedule,0}, + {long_gc,200}, + {nothing, 0}]), + ok; + (_) -> ok + end), Config. -end_per_suite(_Config) -> +end_per_testcase(_, _Config) -> emqx_ct_helpers:stop_apps([]). t_sys_mon(_Config) -> @@ -55,6 +80,13 @@ t_sys_mon(_Config) -> validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) end, ?INPUTINFO). +t_sys_mon2(_Config) -> + ?SYSMON ! {timeout, ignored, reset}, + ?SYSMON ! {ignored}, + ?assertEqual(ignored, gen_server:call(?SYSMON, ignored)), + ?assertEqual(ok, gen_server:cast(?SYSMON, ignored)), + gen_server:stop(?SYSMON). + validate_sys_mon_info(PidOrPort, SysMonName,ValidateInfo, InfoOrPort) -> {ok, C} = emqtt:start_link([{host, "localhost"}]), {ok, _} = emqtt:connect(C), diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl index 5806f6f73..d2fee67d8 100644 --- a/test/emqx_ws_connection_SUITE.erl +++ b/test/emqx_ws_connection_SUITE.erl @@ -106,7 +106,7 @@ t_websocket_init(_) -> t_websocket_handle_binary(_) -> with_ws_conn(fun(WsConn) -> - ok = meck:expect(emqx_channel, recvd, fun(_Oct, Channel) -> {ok, Channel} end), + ok = meck:expect(emqx_channel, recvd, fun(_Oct, Channel) -> Channel end), {ok, WsConn} = websocket_handle({binary, [<<>>]}, WsConn) end). diff --git a/test/emqx_zone_SUITE.erl b/test/emqx_zone_SUITE.erl index c56a68c51..1c393de80 100644 --- a/test/emqx_zone_SUITE.erl +++ b/test/emqx_zone_SUITE.erl @@ -56,6 +56,7 @@ init_per_suite(Config) -> Config. end_per_suite(_Config) -> + emqx_zone:unset_all_env(), application:unset_env(emqx, zone_env), application:unset_env(emqx, zones). From 73bfaa038b266a886e9b0801f71fbaf7f0b0861e Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Fri, 25 Oct 2019 10:08:13 +0800 Subject: [PATCH 12/40] Add more test cases for emqx_packet --- test/emqx_packet_SUITE.erl | 45 ++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/test/emqx_packet_SUITE.erl b/test/emqx_packet_SUITE.erl index bcc4439b1..43b7b5553 100644 --- a/test/emqx_packet_SUITE.erl +++ b/test/emqx_packet_SUITE.erl @@ -82,17 +82,20 @@ t_check_publish(_) -> Props = #{'Response-Topic' => <<"responsetopic">>, 'Topic-Alias' => 1}, ok = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, Props, <<"payload">>)), ok = emqx_packet:check(#mqtt_packet_publish{packet_id = 1, topic_name = <<"t">>}), + ok = emqx_packet:check(#mqtt_packet_publish{topic_name = <<>>, properties = #{'Topic-Alias'=> 0}}), {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<>>, 1, #{}, <<"payload">>)), {error, ?RC_TOPIC_NAME_INVALID} = emqx_packet:check(?PUBLISH_PACKET(?QOS_1, <<"+/+">>, 1, #{}, <<"payload">>)), {error, ?RC_TOPIC_ALIAS_INVALID} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Topic-Alias' => 0}, <<"payload">>)), %% TODO:: %% {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)), ok = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 10}, <<"payload">>)), + {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Subscription-Identifier' => 0}, <<"payload">>)), {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check(?PUBLISH_PACKET(1, <<"topic">>, 1, #{'Response-Topic' => <<"+/+">>}, <<"payload">>)). t_check_subscribe(_) -> ok = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => 1}, [{<<"topic">>, #{qos => ?QOS_0}}])), + {error, ?RC_TOPIC_FILTER_INVALID} = emqx_packet:check(#mqtt_packet_subscribe{topic_filters = []}), {error, ?RC_SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED} = emqx_packet:check(?SUBSCRIBE_PACKET(1, #{'Subscription-Identifier' => -1}, [{<<"topic">>, #{qos => ?QOS_0, rp => 0}}])). @@ -104,7 +107,11 @@ t_check_unsubscribe(_) -> t_check_connect(_) -> Opts = #{max_clientid_len => 5, mqtt_retain_available => false}, ok = emqx_packet:check(#mqtt_packet_connect{}, Opts), - ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{properties = #{'Receive-Maximum' => 1}}), Opts), + ok = emqx_packet:check(?CONNECT_PACKET(#mqtt_packet_connect{clientid = <<1>>, + properties = #{'Receive-Maximum' => 1}, + will_flag = true, + will_topic = <<"will_topic">>} + ), Opts), ConnPkt1 = #mqtt_packet_connect{proto_name = <<"MQIsdp">>, proto_ver = ?MQTT_PROTO_V5 }, @@ -137,7 +144,9 @@ t_check_connect(_) -> properties = #{'Request-Problem-Information' => 2}}), Opts), {error, ?RC_PROTOCOL_ERROR} = emqx_packet:check( ?CONNECT_PACKET(#mqtt_packet_connect{ - properties = #{'Receive-Maximum' => 0}}), Opts). + properties = #{'Receive-Maximum' => 0}}), Opts), + ConnPkt7 = #mqtt_packet_connect{clientid = <<>>, clean_start = false}, + {error, ?RC_CLIENT_IDENTIFIER_NOT_VALID} = emqx_packet:check(ConnPkt7, Opts). t_from_to_message(_) -> ExpectedMsg = emqx_message:make(<<"clientid">>, ?QOS_0, <<"topic">>, <<"payload">>), @@ -160,6 +169,7 @@ t_from_to_message(_) -> }). t_will_msg(_) -> + ?assertEqual(undefined, emqx_packet:will_msg(#mqtt_packet_connect{will_flag = false})), Pkt = #mqtt_packet_connect{will_flag = true, clientid = <<"clientid">>, username = "test", @@ -171,14 +181,30 @@ t_will_msg(_) -> }, Msg = emqx_packet:will_msg(Pkt), ?assertEqual(<<"clientid">>, Msg#message.from), - ?assertEqual(<<"topic">>, Msg#message.topic). + ?assertEqual(<<"topic">>, Msg#message.topic), + Pkt2 = #mqtt_packet_connect{will_flag = true, + clientid = <<"clientid">>, + username = "test", + will_retain = true, + will_qos = ?QOS_2, + will_topic = <<"topic">>, + will_props = undefined, + will_payload = <<"payload">> + }, + Msg2 = emqx_packet:will_msg(Pkt2), + ?assertEqual(<<"clientid">>, Msg2#message.from), + ?assertEqual(<<"topic">>, Msg2#message.topic). -t_to_message(_) -> - error('TODO'). - - t_format(_) -> - io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{}))]), + io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK, retain = true, dup = 0}, variable = undefined})]), + io:format("~s", [emqx_packet:format(#mqtt_packet{header = #mqtt_packet_header{type = ?CONNACK}, variable = 1, payload = <<"payload">>})]), + io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{will_flag = true, + will_retain = true, + will_qos = ?QOS_2, + will_topic = <<"topic">>, + will_props = undefined, + will_payload = <<"payload">>}))]), + io:format("~s", [emqx_packet:format(?CONNECT_PACKET(#mqtt_packet_connect{password = password}))]), io:format("~s", [emqx_packet:format(?CONNACK_PACKET(?CONNACK_SERVER))]), io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_1, 1))]), io:format("~s", [emqx_packet:format(?PUBLISH_PACKET(?QOS_2, <<"topic">>, 10, <<"payload">>))]), @@ -187,5 +213,6 @@ t_format(_) -> io:format("~s", [emqx_packet:format(?SUBSCRIBE_PACKET(15, [{<<"topic">>, ?QOS_0}, {<<"topic1">>, ?QOS_1}]))]), io:format("~s", [emqx_packet:format(?SUBACK_PACKET(40, [?QOS_0, ?QOS_1]))]), io:format("~s", [emqx_packet:format(?UNSUBSCRIBE_PACKET(89, [<<"t">>, <<"t2">>]))]), - io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]). + io:format("~s", [emqx_packet:format(?UNSUBACK_PACKET(90))]), + io:format("~s", [emqx_packet:format(?DISCONNECT_PACKET(128))]). From ebef0ec5542daa986b91cfa85a9219aafd5ca52b Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Mon, 28 Oct 2019 07:51:09 +0800 Subject: [PATCH 13/40] Add zone_options module --- src/emqx_time.erl | 52 ----------------- src/emqx_zone_options.erl | 120 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 52 deletions(-) delete mode 100644 src/emqx_time.erl create mode 100644 src/emqx_zone_options.erl diff --git a/src/emqx_time.erl b/src/emqx_time.erl deleted file mode 100644 index 073ee7067..000000000 --- a/src/emqx_time.erl +++ /dev/null @@ -1,52 +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_time). - --export([ seed/0 - , now_secs/0 - , now_secs/1 - , now_ms/0 - , now_ms/1 - ]). - --compile({inline, - [ seed/0 - , now_secs/0 - , now_secs/1 - , now_ms/0 - , now_ms/1 - ]}). - -seed() -> - rand:seed(exsplus, erlang:timestamp()). - --spec(now_secs() -> pos_integer()). -now_secs() -> - erlang:system_time(second). - --spec(now_secs(erlang:timestamp()) -> pos_integer()). -now_secs({MegaSecs, Secs, _MicroSecs}) -> - MegaSecs * 1000000 + Secs. - --spec(now_ms() -> pos_integer()). -now_ms() -> - erlang:system_time(millisecond). - --spec(now_ms(erlang:timestamp()) -> pos_integer()). -now_ms({MegaSecs, Secs, MicroSecs}) -> - (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). - diff --git a/src/emqx_zone_options.erl b/src/emqx_zone_options.erl new file mode 100644 index 000000000..37844bfbb --- /dev/null +++ b/src/emqx_zone_options.erl @@ -0,0 +1,120 @@ +%%-------------------------------------------------------------------- +%% 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_zone_options). + +-compile(inline). + +-include("types.hrl"). +-include("emqx_mqtt.hrl"). + +-export([ idle_timeout/1 + , publish_limit/1 + , mqtt_frame_options/1 + , mqtt_strict_mode/1 + , max_packet_size/1 + , mountpoint/1 + , use_username_as_clientid/1 + , enable_stats/1 + , enable_acl/1 + , enable_ban/1 + , enable_flapping_detect/1 + , ignore_loop_deliver/1 + , server_keepalive/1 + , keepalive_backoff/1 + , max_inflight/1 + , session_expiry_interval/1 + , force_gc_policy/1 + , force_shutdown_policy/1 + ]). + +-import(emqx_zone, [get_env/2, get_env/3]). + +-define(DEFAULT_IDLE_TIMEOUT, 30000). + +-spec(idle_timeout(emqx_zone:zone()) -> pos_integer()). +idle_timeout(Zone) -> + get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT). + +-spec(publish_limit(emqx_zone:zone()) -> maybe(esockd_rate_limit:bucket())). +publish_limit(Zone) -> + get_env(Zone, publish_limit). + +-spec(mqtt_frame_options(emqx_zone:zone()) -> emqx_frame:options()). +mqtt_frame_options(Zone) -> + #{strict_mode => mqtt_strict_mode(Zone), + max_size => max_packet_size(Zone) + }. + +-spec(mqtt_strict_mode(emqx_zone:zone()) -> boolean()). +mqtt_strict_mode(Zone) -> + get_env(Zone, mqtt_strict_mode, false). + +-spec(max_packet_size(emqx_zone:zone()) -> integer()). +max_packet_size(Zone) -> + get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE). + +-spec(mountpoint(emqx_zone:zone()) -> maybe(emqx_mountpoint:mountpoint())). +mountpoint(Zone) -> get_env(Zone, mountpoint). + +-spec(use_username_as_clientid(emqx_zone:zone()) -> boolean()). +use_username_as_clientid(Zone) -> + get_env(Zone, use_username_as_clientid, false). + +-spec(enable_stats(emqx_zone:zone()) -> boolean()). +enable_stats(Zone) -> + get_env(Zone, enable_stats, true). + +-spec(enable_acl(emqx_zone:zone()) -> boolean()). +enable_acl(Zone) -> + get_env(Zone, enable_acl, true). + +-spec(enable_ban(emqx_zone:zone()) -> boolean()). +enable_ban(Zone) -> + get_env(Zone, enable_ban, false). + +-spec(enable_flapping_detect(emqx_zone:zone()) -> boolean()). +enable_flapping_detect(Zone) -> + get_env(Zone, enable_flapping_detect, false). + +-spec(ignore_loop_deliver(emqx_zone:zone()) -> boolean()). +ignore_loop_deliver(Zone) -> + get_env(Zone, ignore_loop_deliver, false). + +-spec(server_keepalive(emqx_zone:zone()) -> pos_integer()). +server_keepalive(Zone) -> + get_env(Zone, server_keepalive). + +-spec(keepalive_backoff(emqx_zone:zone()) -> float()). +keepalive_backoff(Zone) -> + get_env(Zone, keepalive_backoff, 0.75). + +-spec(max_inflight(emqx_zone:zone()) -> 0..65535). +max_inflight(Zone) -> + get_env(Zone, max_inflight, 65535). + +-spec(session_expiry_interval(emqx_zone:zone()) -> non_neg_integer()). +session_expiry_interval(Zone) -> + get_env(Zone, session_expiry_interval, 0). + +-spec(force_gc_policy(emqx_zone:zone()) -> maybe(emqx_gc:opts())). +force_gc_policy(Zone) -> + get_env(Zone, force_gc_policy). + +-spec(force_shutdown_policy(emqx_zone:zone()) -> maybe(emqx_oom:opts())). +force_shutdown_policy(Zone) -> + get_env(Zone, force_shutdown_policy). + From c1d768ff748b003666ddb9306cf13bd2d99bca35 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Mon, 28 Oct 2019 17:50:28 +0800 Subject: [PATCH 14/40] Add API for clean and get acl cache --- src/emqx_acl_cache.erl | 48 ++++++++++++++++++++++++---------- src/emqx_channel.erl | 8 ++++++ test/emqx_connection_SUITE.erl | 20 ++++++++++++++ 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/emqx_acl_cache.erl b/src/emqx_acl_cache.erl index b417f16fa..d94c165e8 100644 --- a/src/emqx_acl_cache.erl +++ b/src/emqx_acl_cache.erl @@ -18,21 +18,31 @@ -include("emqx.hrl"). --export([ get_acl_cache/2 +-export([ list_acl_cache/0 + , get_acl_cache/2 , put_acl_cache/3 , cleanup_acl_cache/0 , empty_acl_cache/0 , dump_acl_cache/0 - , get_cache_size/0 , get_cache_max_size/0 - , get_newest_key/0 - , get_oldest_key/0 - , cache_k/2 - , cache_v/1 + , get_cache_ttl/0 , is_enabled/0 ]). +%% export for test +-export([ cache_k/2 + , cache_v/1 + , get_cache_size/0 + , get_newest_key/0 + , get_oldest_key/0 + ]). + -type(acl_result() :: allow | deny). +-type(system_time() :: integer()). +-type(cache_key() :: {emqx_types:pubsub(), emqx_types:topic()}). +-type(cache_val() :: {acl_result(), system_time()}). + +-type(acl_cache_entry() :: {cache_key(), cache_val()}). %% Wrappers for key and value cache_k(PubSub, Topic)-> {PubSub, Topic}. @@ -42,8 +52,21 @@ cache_v(AclResult)-> {AclResult, time_now()}. is_enabled() -> application:get_env(emqx, enable_acl_cache, true). -%% We'll cleanup the cache before repalcing an expired acl. --spec(get_acl_cache(publish | subscribe, emqx_topic:topic()) -> (acl_result() | not_found)). +-spec(get_cache_max_size() -> integer()). +get_cache_max_size() -> + application:get_env(emqx, acl_cache_max_size, 32). + +-spec(get_cache_ttl() -> integer()). +get_cache_ttl() -> + application:get_env(emqx, acl_cache_ttl, 60000). + +-spec(list_acl_cache() -> [acl_cache_entry()]). +list_acl_cache() -> + cleanup_acl_cache(), + map_acl_cache(fun(Cache) -> Cache end). + +%% We'll cleanup the cache before replacing an expired acl. +-spec(get_acl_cache(emqx_types:pubsub(), emqx_topic:topic()) -> (acl_result() | not_found)). get_acl_cache(PubSub, Topic) -> case erlang:get(cache_k(PubSub, Topic)) of undefined -> not_found; @@ -59,7 +82,7 @@ get_acl_cache(PubSub, Topic) -> %% If the cache get full, and also the latest one %% is expired, then delete all the cache entries --spec(put_acl_cache(publish | subscribe, emqx_topic:topic(), acl_result()) -> ok). +-spec(put_acl_cache(emqx_types:pubsub(), emqx_topic:topic(), acl_result()) -> ok). put_acl_cache(PubSub, Topic, AclResult) -> MaxSize = get_cache_max_size(), true = (MaxSize =/= 0), Size = get_cache_size(), @@ -97,7 +120,7 @@ evict_acl_cache() -> erlang:erase(OldestK), decr_cache_size(). -%% cleanup all the exipired cache entries +%% cleanup all the expired cache entries -spec(cleanup_acl_cache() -> ok). cleanup_acl_cache() -> keys_queue_set( @@ -108,9 +131,6 @@ get_oldest_key() -> get_newest_key() -> keys_queue_pick(queue_rear()). -get_cache_max_size() -> - application:get_env(emqx, acl_cache_max_size, 32). - get_cache_size() -> case erlang:get(acl_cache_size) of undefined -> 0; @@ -215,7 +235,7 @@ queue_rear() -> fun queue:get_r/1. time_now() -> erlang:system_time(millisecond). if_expired(CachedAt, Fun) -> - TTL = application:get_env(emqx, acl_cache_ttl, 60000), + TTL = get_cache_ttl(), Now = time_now(), if (CachedAt + TTL) =< Now -> Fun(true); diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 5f4821897..c2c2cebb7 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -705,6 +705,9 @@ handle_call({takeover, 'end'}, Channel = #channel{session = Session, AllPendings = lists:append(Delivers, Pendings), {stop, {shutdown, takeovered}, AllPendings, Channel}; +handle_call(list_acl_cache, Channel) -> + {reply, emqx_acl_cache:list_acl_cache(), Channel}; + handle_call(Req, Channel) -> ?LOG(error, "Unexpected call: ~p", [Req]), {reply, ignored, Channel}. @@ -757,6 +760,11 @@ handle_info({sock_closed, Reason}, Channel = #channel{conninfo = ConnInfo, shutdown(Reason, Channel2) end; +handle_info(clean_acl_cache, Channel) -> + ?LOG(debug, "clear acl cache"), + ok = emqx_acl_cache:empty_acl_cache(), + {ok, Channel}; + handle_info(Info, Channel) -> ?LOG(error, "Unexpected info: ~p~n", [Info]), error(unexpected_info), diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index 39cc67dee..d6cf8f934 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -31,6 +31,26 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). +t_clean_acl_cache(_Config) -> + {ok, Client} = emqtt:start_link([{clientid, <<"emqx_c">>}]), + {ok, _} = emqtt:connect(Client), + {ok, _, _} = emqtt:subscribe(Client, <<"t2">>, 0), + emqtt:publish(Client, <<"t1">>, <<"{\"x\":1}">>, 0), + ct:sleep(100), + ClientPid = case emqx_cm:lookup_channels(<<"emqx_c">>) of + [Pid] when is_pid(Pid) -> + Pid; + Pids when is_list(Pids) -> + lists:last(Pids); + _ -> {error, not_found} + end, + Caches = gen_server:call(ClientPid, list_acl_cache), + ct:log("acl caches: ~p", [Caches]), + ?assert(length(Caches) > 0), + erlang:send(ClientPid, clean_acl_cache), + ?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))), + emqtt:stop(Client). + t_basic(_) -> Topic = <<"TopicA">>, {ok, C} = emqtt:start_link([{port, 1883}, {clientid, <<"hello">>}]), From 9ef4e62219ca4d72428be3964a7cbf64986630f9 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Mon, 28 Oct 2019 20:37:15 +0800 Subject: [PATCH 15/40] Add more test cases for emqx_alarm_handler --- test/emqx_alarm_handler_SUITE.erl | 12 +++++++++++- test/emqx_stats_SUITE.erl | 12 ------------ test/emqx_topic_SUITE.erl | 4 ---- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/test/emqx_alarm_handler_SUITE.erl b/test/emqx_alarm_handler_SUITE.erl index 3e818f007..e7e8139b9 100644 --- a/test/emqx_alarm_handler_SUITE.erl +++ b/test/emqx_alarm_handler_SUITE.erl @@ -82,8 +82,18 @@ t_alarm_handler(_) -> {ok, ?PUBLISH_PACKET(?QOS_0, Topic2, _, _), <<>>, _} = raw_recv_parse(Data4), - ?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms())) + ?assertEqual(false, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms())), + emqx_alarm_handler:mnesia(copy), + ?assertEqual(true, lists:keymember(alarm_for_test, 1, emqx_alarm_handler:get_alarms(history))), + + alarm_handler:clear_alarm(not_exist), + + gen_event:start({local, alarm_handler_2}, []), + gen_event:add_handler(alarm_handler_2, emqx_alarm_handler, []), + ?assertEqual({error,bad_query}, gen_event:call(alarm_handler_2, emqx_alarm_handler, bad_query)), + ?assertEqual(ok, gen_event:notify(alarm_handler_2, ignored)), + gen_event:stop(alarm_handler_2) end). with_connection(DoFun) -> diff --git a/test/emqx_stats_SUITE.erl b/test/emqx_stats_SUITE.erl index c419366c3..467c21e7b 100644 --- a/test/emqx_stats_SUITE.erl +++ b/test/emqx_stats_SUITE.erl @@ -34,18 +34,6 @@ t_get_error_state(_) -> Conns = emqx_stats:getstats(), ?assertEqual([], Conns). -t_statsfun(_) -> - error('TODO'). - -t_getstats(_) -> - error('TODO'). - -t_getstat(_) -> - error('TODO'). - -t_setstat(_) -> - error('TODO'). - t_get_state(_) -> with_proc(fun() -> ?assertEqual(undefined, emqx_stats:getstat('notExist')), diff --git a/test/emqx_topic_SUITE.erl b/test/emqx_topic_SUITE.erl index 7eb803895..8dd2c5ca0 100644 --- a/test/emqx_topic_SUITE.erl +++ b/test/emqx_topic_SUITE.erl @@ -229,7 +229,3 @@ bench(Case, Fun, Args) -> ]), ct:pal("Time consumed by ~s: ~.3f(us)~nCall ~s per second: ~w", [Case, Time/?N, Case, (?N * 1000000) div Time]). - - -t_match(_) -> - error('TODO'). From 3405dbaf5cf48bad5d189570757945b5df36cf10 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Mon, 28 Oct 2019 21:01:53 +0800 Subject: [PATCH 16/40] Ordered messaging via multiple gen_rpc clients --- src/emqx_broker.erl | 4 ++-- src/emqx_rpc.erl | 19 ++++++++++++++-- test/emqx_rpc_SUITE.erl | 50 +++++++++++++++++++++++++++++++---------- 3 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/emqx_broker.erl b/src/emqx_broker.erl index 8b6693173..951606183 100644 --- a/src/emqx_broker.erl +++ b/src/emqx_broker.erl @@ -258,7 +258,7 @@ aggre(Routes) -> -spec(forward(node(), emqx_types:topic(), emqx_types:delivery(), RPCMode::sync|async) -> emqx_types:deliver_result()). forward(Node, To, Delivery, async) -> - case emqx_rpc:cast(Node, ?BROKER, dispatch, [To, Delivery]) of + case emqx_rpc:cast(To, Node, ?BROKER, dispatch, [To, Delivery]) of true -> ok; {badrpc, Reason} -> ?LOG(error, "Ansync forward msg to ~s failed: ~p", [Node, Reason]), @@ -266,7 +266,7 @@ forward(Node, To, Delivery, async) -> end; forward(Node, To, Delivery, sync) -> - case emqx_rpc:call(Node, ?BROKER, dispatch, [To, Delivery]) of + case emqx_rpc:call(To, Node, ?BROKER, dispatch, [To, Delivery]) of {badrpc, Reason} -> ?LOG(error, "Sync forward msg to ~s failed: ~p", [Node, Reason]), {error, badrpc}; diff --git a/src/emqx_rpc.erl b/src/emqx_rpc.erl index 6af676802..9eb333a7f 100644 --- a/src/emqx_rpc.erl +++ b/src/emqx_rpc.erl @@ -18,8 +18,11 @@ -module(emqx_rpc). -export([ call/4 + , call/5 , cast/4 + , cast/5 , multicall/4 + , multicall/5 ]). -compile({inline, @@ -34,15 +37,27 @@ call(Node, Mod, Fun, Args) -> filter_result(?RPC:call(rpc_node(Node), Mod, Fun, Args)). +call(Key, Node, Mod, Fun, Args) -> + filter_result(?RPC:call(rpc_node({Key, Node}), Mod, Fun, Args)). + multicall(Nodes, Mod, Fun, Args) -> filter_result(?RPC:multicall(rpc_nodes(Nodes), Mod, Fun, Args)). +multicall(Key, Nodes, Mod, Fun, Args) -> + filter_result(?RPC:multicall(rpc_nodes([{Key, Node} || Node <- Nodes]), Mod, Fun, Args)). + cast(Node, Mod, Fun, Args) -> filter_result(?RPC:cast(rpc_node(Node), Mod, Fun, Args)). -rpc_node(Node) -> +cast(Key, Node, Mod, Fun, Args) -> + filter_result(?RPC:cast(rpc_node({Key, Node}), Mod, Fun, Args)). + +rpc_node(Node) when is_atom(Node) -> ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum), - {Node, rand:uniform(ClientNum)}. + {Node, rand:uniform(ClientNum)}; +rpc_node({Key, Node}) when is_atom(Node) -> + ClientNum = application:get_env(gen_rpc, tcp_client_num, ?DefaultClientNum), + {Node, erlang:phash2(Key, ClientNum) + 1}. rpc_nodes(Nodes) -> rpc_nodes(Nodes, []). diff --git a/test/emqx_rpc_SUITE.erl b/test/emqx_rpc_SUITE.erl index 99c5c2532..6b24e3de0 100644 --- a/test/emqx_rpc_SUITE.erl +++ b/test/emqx_rpc_SUITE.erl @@ -24,17 +24,6 @@ all() -> emqx_ct:all(?MODULE). -t_multicall(_) -> - error('TODO'). - -t_cast(_) -> - error('TODO'). - -t_call(_) -> - error('TODO'). - - - t_prop_rpc(_) -> ok = load(), Opts = [{to_file, user}, {numtests, 10}], @@ -42,7 +31,9 @@ t_prop_rpc(_) -> ok = application:set_env(gen_rpc, call_receive_timeout, 1), ok = emqx_logger:set_log_level(emergency), ?assert(proper:quickcheck(prop_node(), Opts)), + ?assert(proper:quickcheck(prop_node_with_key(), Opts)), ?assert(proper:quickcheck(prop_nodes(), Opts)), + ?assert(proper:quickcheck(prop_nodes_with_key(), Opts)), ok = application:stop(gen_rpc), ok = unload(). @@ -57,6 +48,17 @@ prop_node() -> end end). +prop_node_with_key() -> + ?FORALL({Node, Key}, nodename_with_key(), + begin + ?assert(emqx_rpc:cast(Key, Node, erlang, system_time, [])), + case emqx_rpc:call(Key, Node, erlang, system_time, []) of + {badrpc, _Reason} -> true; + Delivery when is_integer(Delivery) -> true; + _Other -> false + end + end). + prop_nodes() -> ?FORALL(Nodes, nodesname(), begin @@ -70,6 +72,19 @@ prop_nodes() -> end end). +prop_nodes_with_key() -> + ?FORALL({Nodes, Key}, nodesname_with_key(), + begin + case emqx_rpc:multicall(Key, Nodes, erlang, system_time, []) of + {badrpc, _Reason} -> true; + {RealResults, RealBadNodes} + when is_list(RealResults); + is_list(RealBadNodes) -> + true; + _Other -> false + end + end). + %%-------------------------------------------------------------------- %% helper %%-------------------------------------------------------------------- @@ -96,8 +111,19 @@ nodename() -> list_to_atom(Node) end). +nodename_with_key() -> + ?LET({NodePrefix, HostName, Key}, + {node_prefix(), hostname(), choose(0, 10)}, + begin + Node = NodePrefix ++ "@" ++ HostName, + {list_to_atom(Node), Key} + end). + nodesname() -> - oneof([list(nodename()), ["emqxct@127.0.0.1"]]). + oneof([list(nodename()), ['emqxct@127.0.0.1']]). + +nodesname_with_key() -> + oneof([{list(nodename()), choose(0, 10)}, {['emqxct@127.0.0.1'], 1}]). node_prefix() -> oneof(["emqxct", text_like()]). From 971a361ea9faa23de7117d44baf31e017399d64e Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Tue, 29 Oct 2019 14:27:47 +0800 Subject: [PATCH 17/40] Add more test cases for emqx_logger and fix bug for emqx_logger --- src/emqx_logger.erl | 9 +++---- test/emqx_logger_SUITE.erl | 53 +++++++++++++++++++++++++------------- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/emqx_logger.erl b/src/emqx_logger.erl index d3216a539..0cca94e21 100644 --- a/src/emqx_logger.erl +++ b/src/emqx_logger.erl @@ -181,12 +181,9 @@ log_hanlder_info(#{id := Id, level := Level, module := logger_std_h, Type =:= standard_error -> {Id, Level, console}; log_hanlder_info(#{id := Id, level := Level, module := logger_std_h, - config := #{type := Type}}) -> - case Type of - {file, Filename} -> {Id, Level, Filename}; - {file, Filename, _Opts} -> {Id, Level, Filename}; - _ -> {Id, Level, unknown} - end; + config := Config = #{type := file}}) -> + {Id, Level, maps:get(file, Config, atom_to_list(Id))}; + log_hanlder_info(#{id := Id, level := Level, module := logger_disk_log_h, config := #{file := Filename}}) -> {Id, Level, Filename}; diff --git a/test/emqx_logger_SUITE.erl b/test/emqx_logger_SUITE.erl index 16f20dc3c..4b0dc2c9a 100644 --- a/test/emqx_logger_SUITE.erl +++ b/test/emqx_logger_SUITE.erl @@ -21,6 +21,8 @@ -include_lib("eunit/include/eunit.hrl"). +-define(LOGGER, emqx_logger). + all() -> emqx_ct:all(?MODULE). init_per_testcase(_TestCase, Config) -> @@ -30,47 +32,62 @@ end_per_testcase(_TestCase, Config) -> Config. t_debug(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:debug("for_test")), + ?assertEqual(ok, ?LOGGER:debug("for_test", [])), + ?assertEqual(ok, ?LOGGER:debug(#{pid => self()}, "for_test", [])). t_info(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:info("for_test")), + ?assertEqual(ok, ?LOGGER:info("for_test", [])), + ?assertEqual(ok, ?LOGGER:info(#{pid => self()}, "for_test", [])). t_warning(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:warning("for_test")), + ?assertEqual(ok, ?LOGGER:warning("for_test", [])), + ?assertEqual(ok, ?LOGGER:warning(#{pid => self()}, "for_test", [])). t_error(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:error("for_test")), + ?assertEqual(ok, ?LOGGER:error("for_test", [])), + ?assertEqual(ok, ?LOGGER:error(#{pid => self()}, "for_test", [])). t_critical(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:critical("for_test")), + ?assertEqual(ok, ?LOGGER:critical("for_test", [])), + ?assertEqual(ok, ?LOGGER:critical(#{pid => self()}, "for_test", [])). t_set_proc_metadata(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:set_proc_metadata(#{pid => self()})). -t_get_primary_log_level(_) -> - error('TODO'). - -t_set_primary_log_level(_) -> - error('TODO'). +t_primary_log_level(_) -> + ?assertEqual(ok, ?LOGGER:set_primary_log_level(debug)), + ?assertEqual(debug, ?LOGGER:get_primary_log_level()). t_get_log_handlers(_) -> - error('TODO'). + ok = logger:add_handler(logger_std_h_for_test, logger_std_h, #{config => #{type => file, file => "logger_std_h_for_test"}}), + ok = logger:add_handler(logger_disk_log_h_for_test, logger_disk_log_h, #{config => #{file => "logger_disk_log_h_for_test"}}), + ?assertMatch([_|_], ?LOGGER:get_log_handlers()). t_get_log_handler(_) -> - error('TODO'). + [{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(), + ?assertMatch({HandlerId, _, _}, ?LOGGER:get_log_handler(HandlerId)). t_set_log_handler_level(_) -> - error('TODO'). + [{HandlerId, _, _} | _ ] = ?LOGGER:get_log_handlers(), + Level = debug, + ?LOGGER:set_log_handler_level(HandlerId, Level), + ?assertMatch({HandlerId, Level, _}, ?LOGGER:get_log_handler(HandlerId)). t_set_log_level(_) -> - error('TODO'). + ?assertMatch({error, _Error}, ?LOGGER:set_log_level(for_test)), + ?assertEqual(ok, ?LOGGER:set_log_level(debug)). t_parse_transform(_) -> error('TODO'). t_set_metadata_peername(_) -> - error('TODO'). + ?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")). t_set_metadata_clientid(_) -> - error('TODO'). - + ?assertEqual(ok, ?LOGGER:set_metadata_clientid(<<>>)), + ?assertEqual(ok, ?LOGGER:set_metadata_clientid("for_test")). From 3009eeb270f593b5107987180cf5c2789240d0f1 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Tue, 29 Oct 2019 15:43:09 +0800 Subject: [PATCH 18/40] Add more test cases for emqx_zone --- test/emqx_zone_SUITE.erl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/emqx_zone_SUITE.erl b/test/emqx_zone_SUITE.erl index 1c393de80..574b027ec 100644 --- a/test/emqx_zone_SUITE.erl +++ b/test/emqx_zone_SUITE.erl @@ -100,3 +100,12 @@ t_uncovered_func(_) -> ok = Pid ! ok, emqx_zone:stop(). +t_frame_options(_) -> + ?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:frame_options(zone)). + +t_check_oom(_) -> + {ok, _} = emqx_zone:start_link(), + application:set_env(emqx, zones, [{zone, ?ENVS}]), + ok = emqx_zone:force_reload(), + ?assertEqual(ok, emqx_zone:check_oom(zone, fun() -> ok end)), + emqx_zone:stop(). \ No newline at end of file From d444d1c61c74c5c66300390358fb97e20858db74 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Wed, 30 Oct 2019 14:48:17 +0800 Subject: [PATCH 19/40] Add more test cases for emqx_vm_mon --- test/emqx_vm_mon_SUITE.erl | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/test/emqx_vm_mon_SUITE.erl b/test/emqx_vm_mon_SUITE.erl index cb85b1fac..2b26c8078 100644 --- a/test/emqx_vm_mon_SUITE.erl +++ b/test/emqx_vm_mon_SUITE.erl @@ -39,18 +39,6 @@ init_per_suite(Config) -> end_per_suite(_Config) -> application:stop(sasl). - t_get_process_high_watermark(_) -> - error('TODO'). - -t_set_process_high_watermark(_) -> - error('TODO'). - -t_get_process_low_watermark(_) -> - error('TODO'). - -t_set_process_low_watermark(_) -> - error('TODO'). - t_api(_) -> meck:new(alarm_handler, [passthrough, no_history]), Tester = self(), @@ -70,8 +58,13 @@ t_api(_) -> end), gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}), {ok, _} = emqx_vm_mon:start_link([{check_interval, 1}, - {process_high_watermark, 0}, - {process_low_watermark, 0.6}]), + {process_high_watermark, 0.8}, + {process_low_watermark, 0.75}]), + timer:sleep(emqx_vm_mon:get_check_interval() * 1000), + emqx_vm_mon:set_process_high_watermark(0.0), + emqx_vm_mon:set_process_low_watermark(0.6), + ?assertEqual(0.0, emqx_vm_mon:get_process_high_watermark()), + ?assertEqual(0.6, emqx_vm_mon:get_process_low_watermark()), ?WAIT({Ref, set_alarm, {too_many_processes, _Count}}, 2000), ?assertEqual(true, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())), emqx_vm_mon:set_process_high_watermark(0.8), @@ -81,7 +74,10 @@ t_api(_) -> ?WAIT({Ref, clear_alarm, too_many_processes}, 3000), ?assertEqual(false, lists:keymember(too_many_processes, 1, alarm_handler:get_alarms())), emqx_vm_mon:set_check_interval(20), - ?assertEqual(20, emqx_vm_mon:get_check_interval()) + ?assertEqual(20, emqx_vm_mon:get_check_interval()), + ?assertEqual(ignored, gen_server:call(emqx_vm_mon, ignored)), + ?assertEqual(ok, gen_server:cast(emqx_vm_mon, ignored)), + ?assertEqual(ignored, emqx_vm_mon ! ignored) after meck:unload(alarm_handler) end. From bf82c5a1fe9545395c05075087587497feeddf44 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Wed, 30 Oct 2019 15:48:32 +0800 Subject: [PATCH 20/40] Add more test cases for emqx_tracer --- test/emqx_tracer_SUITE.erl | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/test/emqx_tracer_SUITE.erl b/test/emqx_tracer_SUITE.erl index 3936c05fe..280d580c5 100644 --- a/test/emqx_tracer_SUITE.erl +++ b/test/emqx_tracer_SUITE.erl @@ -47,9 +47,12 @@ t_start_traces(_Config) -> {error, _} = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"), emqx_logger:set_log_level(debug), ok = emqx_tracer:start_trace({clientid, <<"client">>}, debug, "tmp/client.log"), - ok = emqx_tracer:start_trace({clientid, <<"client2">>}, all, "tmp/client2.log"), - {error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client3">>}, bad_level, "tmp/client3.log"), + ok = emqx_tracer:start_trace({clientid, "client2"}, all, "tmp/client2.log"), + ok = emqx_tracer:start_trace({clientid, client3}, all, "tmp/client3.log"), + {error, {invalid_log_level, bad_level}} = emqx_tracer:start_trace({clientid, <<"client4">>}, bad_level, "tmp/client4.log"), + {error, {handler_not_added, {file_error,".",eisdir}}} = emqx_tracer:start_trace({clientid, <<"client5">>}, debug, "."), ok = emqx_tracer:start_trace({topic, <<"a/#">>}, all, "tmp/topic_trace.log"), + ok = emqx_tracer:start_trace({topic, <<"b/#">>}, all, "tmp/topic_trace.log"), ct:sleep(100), %% Verify the tracing file exits @@ -60,7 +63,9 @@ t_start_traces(_Config) -> %% Get current traces ?assertEqual([{{clientid,"client"},{debug,"tmp/client.log"}}, {{clientid,"client2"},{debug,"tmp/client2.log"}}, - {{topic,"a/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()), + {{clientid,"client3"},{debug,"tmp/client3.log"}}, + {{topic,"a/#"},{debug,"tmp/topic_trace.log"}}, + {{topic,"b/#"},{debug,"tmp/topic_trace.log"}}], emqx_tracer:lookup_traces()), %% set the overall log level to debug emqx_logger:set_log_level(debug), @@ -77,23 +82,11 @@ t_start_traces(_Config) -> %% Stop tracing ok = emqx_tracer:stop_trace({clientid, <<"client">>}), ok = emqx_tracer:stop_trace({clientid, <<"client2">>}), + ok = emqx_tracer:stop_trace({clientid, <<"client3">>}), ok = emqx_tracer:stop_trace({topic, <<"a/#">>}), + ok = emqx_tracer:stop_trace({topic, <<"b/#">>}), + {error, _Reason} = emqx_tracer:stop_trace({topic, <<"c/#">>}), emqtt:disconnect(T), emqx_logger:set_log_level(warning). - - -t_start_trace(_) -> - error('TODO'). - -t_stop_trace(_) -> - error('TODO'). - -t_lookup_traces(_) -> - error('TODO'). - - - -t_trace(_) -> - error('TODO'). - + \ No newline at end of file From 4c9dda105fd332b22a43eb702aaa32c418e680ec Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:00:11 +0800 Subject: [PATCH 21/40] Add 'active_n' option for WebSocket listener --- etc/emqx.conf | 14 ++++- priv/emqx.schema | 19 ++++-- src/emqx_zone_options.erl | 120 -------------------------------------- test/emqx_time_SUITE.erl | 34 ----------- 4 files changed, 27 insertions(+), 160 deletions(-) delete mode 100644 src/emqx_zone_options.erl delete mode 100644 test/emqx_time_SUITE.erl diff --git a/etc/emqx.conf b/etc/emqx.conf index c2d2ad75f..6f28bf59d 100644 --- a/etc/emqx.conf +++ b/etc/emqx.conf @@ -1349,9 +1349,14 @@ listener.ws.external.max_connections = 102400 ## Value: Number listener.ws.external.max_conn_rate = 1000 +## Simulate the {active, N} option for the MQTT/WebSocket connections. +## +## Value: Number +listener.ws.external.active_n = 100 + ## Rate limit for the MQTT/WebSocket connections. ## -## Value: limit,duration +## Value: Limit,Duration ## Default: 100KB incoming per 10 seconds. ## listener.ws.external.rate_limit = 100KB,10s @@ -1557,9 +1562,14 @@ listener.wss.external.max_connections = 16 ## Value: Number listener.wss.external.max_conn_rate = 1000 +## Simulate the {active, N} option for the MQTT/WebSocket/SSL connections. +## +## Value: Number +listener.wss.external.active_n = 100 + ## Rate limit for the MQTT/WebSocket/SSL connections. ## -## Value: limit,duration +## Value: Limit,Duration ## Default: 100KB incoming per 10 seconds. ## listener.wss.external.rate_limit = 100KB,10s diff --git a/priv/emqx.schema b/priv/emqx.schema index d751ecd06..6ac622ea7 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -959,17 +959,18 @@ end}. {force_gc_policy, GcPolicy}; ("force_shutdown_policy", "default") -> {DefaultLen, DefaultSize} = - case erlang:system_info(wordsize) of + case WordSize = erlang:system_info(wordsize) of 8 -> % arch_64 {8000, cuttlefish_bytesize:parse("800MB")}; 4 -> % arch_32 {1000, cuttlefish_bytesize:parse("100MB")} end, {force_shutdown_policy, #{message_queue_len => DefaultLen, - max_heap_size => DefaultSize}}; + max_heap_size => DefaultSize div WordSize + }}; ("force_shutdown_policy", Val) -> [Len, Siz] = string:tokens(Val, "| "), - MaxSiz = case erlang:system_info(wordsize) of + MaxSiz = case WordSize = erlang:system_info(wordsize) of 8 -> % arch_64 (1 bsl 59) - 1; 4 -> % arch_32 @@ -983,7 +984,7 @@ end}. cuttlefish:invalid(io_lib:format("force_shutdown_policy: heap-size ~s is too large", [Siz])); Siz1 -> #{message_queue_len => list_to_integer(Len), - max_heap_size => Siz1} + max_heap_size => Siz1 div WordSize} end, {force_shutdown_policy, ShutdownPolicy}; ("mqueue_priorities", Val) -> @@ -1289,6 +1290,11 @@ end}. {datatype, integer} ]}. +{mapping, "listener.ws.$name.active_n", "emqx.listeners", [ + {default, 100}, + {datatype, integer} +]}. + {mapping, "listener.ws.$name.zone", "emqx.listeners", [ {datatype, string} ]}. @@ -1442,6 +1448,11 @@ end}. {datatype, integer} ]}. +{mapping, "listener.wss.$name.active_n", "emqx.listeners", [ + {default, 100}, + {datatype, integer} +]}. + {mapping, "listener.wss.$name.zone", "emqx.listeners", [ {datatype, string} ]}. diff --git a/src/emqx_zone_options.erl b/src/emqx_zone_options.erl deleted file mode 100644 index 37844bfbb..000000000 --- a/src/emqx_zone_options.erl +++ /dev/null @@ -1,120 +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_zone_options). - --compile(inline). - --include("types.hrl"). --include("emqx_mqtt.hrl"). - --export([ idle_timeout/1 - , publish_limit/1 - , mqtt_frame_options/1 - , mqtt_strict_mode/1 - , max_packet_size/1 - , mountpoint/1 - , use_username_as_clientid/1 - , enable_stats/1 - , enable_acl/1 - , enable_ban/1 - , enable_flapping_detect/1 - , ignore_loop_deliver/1 - , server_keepalive/1 - , keepalive_backoff/1 - , max_inflight/1 - , session_expiry_interval/1 - , force_gc_policy/1 - , force_shutdown_policy/1 - ]). - --import(emqx_zone, [get_env/2, get_env/3]). - --define(DEFAULT_IDLE_TIMEOUT, 30000). - --spec(idle_timeout(emqx_zone:zone()) -> pos_integer()). -idle_timeout(Zone) -> - get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT). - --spec(publish_limit(emqx_zone:zone()) -> maybe(esockd_rate_limit:bucket())). -publish_limit(Zone) -> - get_env(Zone, publish_limit). - --spec(mqtt_frame_options(emqx_zone:zone()) -> emqx_frame:options()). -mqtt_frame_options(Zone) -> - #{strict_mode => mqtt_strict_mode(Zone), - max_size => max_packet_size(Zone) - }. - --spec(mqtt_strict_mode(emqx_zone:zone()) -> boolean()). -mqtt_strict_mode(Zone) -> - get_env(Zone, mqtt_strict_mode, false). - --spec(max_packet_size(emqx_zone:zone()) -> integer()). -max_packet_size(Zone) -> - get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE). - --spec(mountpoint(emqx_zone:zone()) -> maybe(emqx_mountpoint:mountpoint())). -mountpoint(Zone) -> get_env(Zone, mountpoint). - --spec(use_username_as_clientid(emqx_zone:zone()) -> boolean()). -use_username_as_clientid(Zone) -> - get_env(Zone, use_username_as_clientid, false). - --spec(enable_stats(emqx_zone:zone()) -> boolean()). -enable_stats(Zone) -> - get_env(Zone, enable_stats, true). - --spec(enable_acl(emqx_zone:zone()) -> boolean()). -enable_acl(Zone) -> - get_env(Zone, enable_acl, true). - --spec(enable_ban(emqx_zone:zone()) -> boolean()). -enable_ban(Zone) -> - get_env(Zone, enable_ban, false). - --spec(enable_flapping_detect(emqx_zone:zone()) -> boolean()). -enable_flapping_detect(Zone) -> - get_env(Zone, enable_flapping_detect, false). - --spec(ignore_loop_deliver(emqx_zone:zone()) -> boolean()). -ignore_loop_deliver(Zone) -> - get_env(Zone, ignore_loop_deliver, false). - --spec(server_keepalive(emqx_zone:zone()) -> pos_integer()). -server_keepalive(Zone) -> - get_env(Zone, server_keepalive). - --spec(keepalive_backoff(emqx_zone:zone()) -> float()). -keepalive_backoff(Zone) -> - get_env(Zone, keepalive_backoff, 0.75). - --spec(max_inflight(emqx_zone:zone()) -> 0..65535). -max_inflight(Zone) -> - get_env(Zone, max_inflight, 65535). - --spec(session_expiry_interval(emqx_zone:zone()) -> non_neg_integer()). -session_expiry_interval(Zone) -> - get_env(Zone, session_expiry_interval, 0). - --spec(force_gc_policy(emqx_zone:zone()) -> maybe(emqx_gc:opts())). -force_gc_policy(Zone) -> - get_env(Zone, force_gc_policy). - --spec(force_shutdown_policy(emqx_zone:zone()) -> maybe(emqx_oom:opts())). -force_shutdown_policy(Zone) -> - get_env(Zone, force_shutdown_policy). - diff --git a/test/emqx_time_SUITE.erl b/test/emqx_time_SUITE.erl deleted file mode 100644 index e190c7f78..000000000 --- a/test/emqx_time_SUITE.erl +++ /dev/null @@ -1,34 +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_time_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --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())). - From 223163d5b9366ed429d587a24058163e6832ea50 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:00:35 +0800 Subject: [PATCH 22/40] Remove the 'emqx_oom' module --- src/emqx_oom.erl | 97 ------------------------------------------------ 1 file changed, 97 deletions(-) delete mode 100644 src/emqx_oom.erl diff --git a/src/emqx_oom.erl b/src/emqx_oom.erl deleted file mode 100644 index efc0a4c69..000000000 --- a/src/emqx_oom.erl +++ /dev/null @@ -1,97 +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. -%%-------------------------------------------------------------------- - -%%-------------------------------------------------------------------- -%% @doc OOM (Out Of Memory) monitor for the channel process. -%% @end -%%-------------------------------------------------------------------- - --module(emqx_oom). - --include("types.hrl"). - --export([ init/1 - , check/1 - , info/1 - ]). - --export_type([opts/0, oom_policy/0]). - --type(opts() :: #{message_queue_len => non_neg_integer(), - max_heap_size => non_neg_integer() - }). - --opaque(oom_policy() :: {oom_policy, opts()}). - --type(reason() :: message_queue_too_long|proc_heap_too_large). - --define(DISABLED, 0). - -%% @doc Init the OOM policy. --spec(init(opts()) -> oom_policy()). -init(#{message_queue_len := MaxQLen, - max_heap_size := MaxHeapSizeInBytes}) -> - MaxHeapSize = MaxHeapSizeInBytes div erlang:system_info(wordsize), - %% If set to zero, the limit is disabled. - _ = erlang:process_flag(max_heap_size, #{size => MaxHeapSize, - kill => false, - error_logger => true - }), - {oom_policy, #{message_queue_len => MaxQLen, - max_heap_size => MaxHeapSize - }}. - -%% @doc Check self() process status against channel process management policy, -%% return `ok | {shutdown, Reason}' accordingly. -%% `ok': There is nothing out of the ordinary. -%% `shutdown': Some numbers (message queue length hit the limit), -%% hence shutdown for greater good (system stability). --spec(check(oom_policy()) -> ok | {shutdown, reason()}). -check({oom_policy, #{message_queue_len := MaxQLen, - max_heap_size := MaxHeapSize}}) -> - Qlength = proc_info(message_queue_len), - HeapSize = proc_info(total_heap_size), - do_check([{fun() -> is_exceeded(Qlength, MaxQLen) end, - {shutdown, message_queue_too_long}}, - {fun() -> is_exceeded(HeapSize, MaxHeapSize) end, - {shutdown, proc_heap_too_large}}]). - -do_check([]) -> ok; -do_check([{Pred, Result} | Rest]) -> - case Pred() of - true -> Result; - false -> do_check(Rest) - end. - --spec(info(oom_policy()) -> opts()). -info({oom_policy, Opts}) -> Opts. - --compile({inline, - [ is_exceeded/2 - , is_enabled/1 - , proc_info/1 - ]}). - -is_exceeded(Val, Max) -> - is_enabled(Max) andalso Val > Max. - -is_enabled(Max) -> - is_integer(Max) andalso Max > ?DISABLED. - -proc_info(Key) -> - {Key, Value} = erlang:process_info(self(), Key), - Value. - From 605a03453efb3a412fb52f772b8f31ac6b9dbaaa Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:07:34 +0800 Subject: [PATCH 23/40] Remove the 'emqx_time' module and use 'erlang:system_time/1' --- src/emqx_alarm_handler.erl | 2 +- src/emqx_flapping.erl | 9 +++++---- src/emqx_mod_presence.erl | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/emqx_alarm_handler.erl b/src/emqx_alarm_handler.erl index c01003c0d..2131bd177 100644 --- a/src/emqx_alarm_handler.erl +++ b/src/emqx_alarm_handler.erl @@ -158,7 +158,7 @@ encode_alarm({AlarmId, #alarm{severity = Severity, {desc, [{severity, Severity}, {title, iolist_to_binary(Title)}, {summary, iolist_to_binary(Summary)}, - {timestamp, emqx_time:now_ms(Ts)}]}]); + {timestamp, emqx_misc:now_to_secs(Ts)}]}]); encode_alarm({AlarmId, undefined}) -> emqx_json:safe_encode([{id, maybe_to_binary(AlarmId)}]); encode_alarm({AlarmId, AlarmDesc}) -> diff --git a/src/emqx_flapping.erl b/src/emqx_flapping.erl index e058f0cda..93c0a3194 100644 --- a/src/emqx_flapping.erl +++ b/src/emqx_flapping.erl @@ -99,7 +99,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost}, %% Create a flapping record. Flapping = #flapping{clientid = ClientId, peerhost = PeerHost, - started_at = emqx_time:now_ms(), + started_at = erlang:system_time(millisecond), detect_cnt = 1 }, true = ets:insert(?FLAPPING_TAB, Flapping), @@ -111,7 +111,7 @@ detect(#{clientid := ClientId, peerhost := PeerHost}, get_policy() -> emqx:get_env(flapping_detect_policy, ?DEFAULT_DETECT_POLICY). -now_diff(TS) -> emqx_time:now_ms() - TS. +now_diff(TS) -> erlang:system_time(millisecond) - TS. %%-------------------------------------------------------------------- %% gen_server callbacks @@ -143,7 +143,7 @@ handle_cast({detected, Flapping = #flapping{clientid = ClientId, [ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Duration]), %% Banned. BannedFlapping = Flapping#flapping{clientid = {banned, ClientId}, - banned_at = emqx_time:now_ms() + banned_at = erlang:system_time(millisecond) }, alarm_handler:set_alarm({{flapping_detected, ClientId}, BannedFlapping}), ets:insert(?FLAPPING_TAB, BannedFlapping); @@ -160,7 +160,8 @@ handle_cast(Msg, State) -> handle_info({timeout, TRef, expire_flapping}, State = #{tref := TRef}) -> with_flapping_tab(fun expire_flapping/2, - [emqx_time:now_ms(), get_policy()]), + [erlang:system_time(millisecond), + get_policy()]), {noreply, ensure_timer(State#{tref => undefined}), hibernate}; handle_info(Info, State) -> diff --git a/src/emqx_mod_presence.erl b/src/emqx_mod_presence.erl index d9f6d5c75..b1e3ff8b0 100644 --- a/src/emqx_mod_presence.erl +++ b/src/emqx_mod_presence.erl @@ -62,7 +62,7 @@ on_client_connected(ClientInfo, ConnAck, ConnInfo, Env) -> connack => ConnAck, clean_start => CleanStart, expiry_interval => ExpiryInterval, - ts => emqx_time:now_ms() + ts => erlang:system_time(millisecond) }, case emqx_json:safe_encode(Presence) of {ok, Payload} -> @@ -78,7 +78,7 @@ on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) -> Presence = #{clientid => ClientId, username => Username, reason => reason(Reason), - ts => emqx_time:now_ms() + ts => erlang:system_time(millisecond) }, case emqx_json:safe_encode(Presence) of {ok, Payload} -> From 30adfc18e684df3e9f470fc8943aa165e0cfd9da Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:08:38 +0800 Subject: [PATCH 24/40] Remove 'gc_state' and 'pub_stats' from channel's state --- src/emqx_channel.erl | 127 ++++++++++--------------------------------- 1 file changed, 29 insertions(+), 98 deletions(-) diff --git a/src/emqx_channel.erl b/src/emqx_channel.erl index 99a595929..eedeacf06 100644 --- a/src/emqx_channel.erl +++ b/src/emqx_channel.erl @@ -40,15 +40,11 @@ , handle_in/2 , handle_out/2 , handle_call/2 - , handle_info/2 , handle_timeout/3 + , handle_info/2 , terminate/2 ]). --export([ recvd/2 - , sent/2 - ]). - %% export for ct -export([set_field/3]). @@ -75,14 +71,10 @@ topic_aliases :: maybe(map()), %% MQTT Topic Alias Maximum alias_maximum :: maybe(map()), - %% Publish Stats - pub_stats :: emqx_types:stats(), %% Timers timers :: #{atom() => disabled | maybe(reference())}, %% Conn State conn_state :: conn_state(), - %% GC State - gc_state :: maybe(emqx_gc:gc_state()), %% Takeover takeover :: boolean(), %% Resume @@ -103,7 +95,6 @@ -type(output() :: emqx_types:packet() | action() | [action()]). -define(TIMER_TABLE, #{ - stats_timer => emit_stats, alive_timer => keepalive, retry_timer => retry_delivery, await_timer => expire_awaiting_rel, @@ -113,8 +104,7 @@ -define(ATTR_KEYS, [conninfo, clientinfo, session, conn_state]). --define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases, - alias_maximum, gc_state]). +-define(INFO_KEYS, ?ATTR_KEYS ++ [keepalive, will_msg, topic_aliases, alias_maximum]). %%-------------------------------------------------------------------- %% Info, Attrs and Caps @@ -146,12 +136,8 @@ info(will_msg, #channel{will_msg = undefined}) -> undefined; info(will_msg, #channel{will_msg = WillMsg}) -> emqx_message:to_map(WillMsg); -info(pub_stats, #channel{pub_stats = PubStats}) -> - PubStats; info(timers, #channel{timers = Timers}) -> - Timers; -info(gc_state, #channel{gc_state = GcState}) -> - maybe_apply(fun emqx_gc:info/1, GcState). + Timers. %% @doc Get attrs of the channel. -spec(attrs(channel()) -> emqx_types:attrs()). @@ -164,10 +150,8 @@ attrs(session, #channel{session = Session}) -> attrs(Key, Channel) -> info(Key, Channel). -spec(stats(channel()) -> emqx_types:stats()). -stats(#channel{pub_stats = PubStats, session = undefined}) -> - maps:to_list(PubStats); -stats(#channel{pub_stats = PubStats, session = Session}) -> - maps:to_list(PubStats) ++ emqx_session:stats(Session). +stats(#channel{session = Session})-> + emqx_session:stats(Session). -spec(caps(channel()) -> emqx_types:caps()). caps(#channel{clientinfo = #{zone := Zone}}) -> @@ -194,7 +178,7 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) -> _ -> undefined end, Protocol = maps:get(protocol, ConnInfo, mqtt), - MountPoint = emqx_zone:get_env(Zone, mountpoint), + MountPoint = emqx_zone:mountpoint(Zone), ClientInfo = #{zone => Zone, protocol => Protocol, peerhost => PeerHost, @@ -205,16 +189,10 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) -> is_bridge => false, is_superuser => false }, - StatsTimer = case emqx_zone:enable_stats(Zone) of - true -> undefined; - false -> disabled - end, #channel{conninfo = ConnInfo, clientinfo = ClientInfo, - pub_stats = #{}, - timers = #{stats_timer => StatsTimer}, + timers = #{}, conn_state = idle, - gc_state = init_gc_state(Zone), takeover = false, resuming = false, pendings = [] @@ -223,17 +201,10 @@ init(ConnInfo = #{peername := {PeerHost, _Port}}, Options) -> peer_cert_as_username(Options) -> proplists:get_value(peer_cert_as_username, Options). -init_gc_state(Zone) -> - maybe_apply(fun emqx_gc:init/1, emqx_zone:force_gc_policy(Zone)). - %%-------------------------------------------------------------------- %% Handle incoming packet %%-------------------------------------------------------------------- --spec(recvd(pos_integer(), channel()) -> channel()). -recvd(Bytes, Channel) -> - ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)). - -spec(handle_in(emqx_types:packet(), channel()) -> {ok, channel()} | {ok, output(), channel()} @@ -258,74 +229,69 @@ handle_in(?CONNECT_PACKET(ConnPkt), Channel) -> end; handle_in(Packet = ?PUBLISH_PACKET(_QoS), Channel) -> - NChannel = inc_pub_stats(publish_in, Channel), case emqx_packet:check(Packet) of - ok -> handle_publish(Packet, NChannel); + ok -> handle_publish(Packet, Channel); {error, ReasonCode} -> - handle_out(disconnect, ReasonCode, NChannel) + handle_out(disconnect, ReasonCode, Channel) end; handle_in(?PUBACK_PACKET(PacketId, _ReasonCode), Channel = #channel{clientinfo = ClientInfo, session = Session}) -> - NChannel = inc_pub_stats(puback_in, Channel), case emqx_session:puback(PacketId, Session) of {ok, Msg, Publishes, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), - handle_out({publish, Publishes}, NChannel#channel{session = NSession}); + handle_out({publish, Publishes}, Channel#channel{session = NSession}); {ok, Msg, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), - {ok, NChannel#channel{session = NSession}}; + {ok, Channel#channel{session = NSession}}; {error, ?RC_PACKET_IDENTIFIER_IN_USE} -> ?LOG(warning, "The PUBACK PacketId ~w is inuse.", [PacketId]), ok = emqx_metrics:inc('packets.puback.inuse'), - {ok, NChannel}; + {ok, Channel}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBACK PacketId ~w is not found", [PacketId]), ok = emqx_metrics:inc('packets.puback.missed'), - {ok, NChannel} + {ok, Channel} end; handle_in(?PUBREC_PACKET(PacketId, _ReasonCode), Channel = #channel{clientinfo = ClientInfo, session = Session}) -> - Channel1 = inc_pub_stats(pubrec_in, Channel), case emqx_session:pubrec(PacketId, Session) of {ok, Msg, NSession} -> ok = emqx_hooks:run('message.acked', [ClientInfo, Msg]), - NChannel = Channel1#channel{session = NSession}, + NChannel = Channel#channel{session = NSession}, handle_out(pubrel, {PacketId, ?RC_SUCCESS}, NChannel); {error, RC = ?RC_PACKET_IDENTIFIER_IN_USE} -> ?LOG(warning, "The PUBREC PacketId ~w is inuse.", [PacketId]), ok = emqx_metrics:inc('packets.pubrec.inuse'), - handle_out(pubrel, {PacketId, RC}, Channel1); + handle_out(pubrel, {PacketId, RC}, Channel); {error, RC = ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBREC ~w is not found.", [PacketId]), ok = emqx_metrics:inc('packets.pubrec.missed'), - handle_out(pubrel, {PacketId, RC}, Channel1) + handle_out(pubrel, {PacketId, RC}, Channel) end; handle_in(?PUBREL_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> - Channel1 = inc_pub_stats(pubrel_in, Channel), case emqx_session:pubrel(PacketId, Session) of {ok, NSession} -> - Channel2 = Channel1#channel{session = NSession}, - handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, Channel2); + NChannel = Channel#channel{session = NSession}, + handle_out(pubcomp, {PacketId, ?RC_SUCCESS}, NChannel); {error, NotFound} -> ok = emqx_metrics:inc('packets.pubrel.missed'), ?LOG(warning, "The PUBREL PacketId ~w is not found", [PacketId]), - handle_out(pubcomp, {PacketId, NotFound}, Channel1) + handle_out(pubcomp, {PacketId, NotFound}, Channel) end; handle_in(?PUBCOMP_PACKET(PacketId, _ReasonCode), Channel = #channel{session = Session}) -> - Channel1 = inc_pub_stats(pubcomp_in, Channel), case emqx_session:pubcomp(PacketId, Session) of {ok, Publishes, NSession} -> - handle_out({publish, Publishes}, Channel1#channel{session = NSession}); + handle_out({publish, Publishes}, Channel#channel{session = NSession}); {ok, NSession} -> - {ok, Channel1#channel{session = NSession}}; + {ok, Channel#channel{session = NSession}}; {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} -> ?LOG(warning, "The PUBCOMP PacketId ~w is not found", [PacketId]), ok = emqx_metrics:inc('packets.pubcomp.missed'), - {ok, Channel1} + {ok, Channel} end; handle_in(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, TopicFilters), @@ -422,11 +388,6 @@ process_connect(ConnPkt = #mqtt_packet_connect{clean_start = CleanStart}, %% Process Publish %%-------------------------------------------------------------------- -inc_pub_stats(Key, Channel) -> inc_pub_stats(Key, 1, Channel). -inc_pub_stats(Key, I, Channel = #channel{pub_stats = PubStats}) -> - NPubStats = maps:update_with(Key, fun(V) -> V+I end, I, PubStats), - Channel#channel{pub_stats = NPubStats}. - handle_publish(Packet = ?PUBLISH_PACKET(_QoS, Topic, _PacketId), Channel = #channel{conninfo = #{proto_ver := ProtoVer}}) -> case pipeline([fun process_alias/2, @@ -540,10 +501,6 @@ do_unsubscribe(TopicFilter, _SubOpts, Channel = %% Handle outgoing packet %%-------------------------------------------------------------------- --spec(sent(pos_integer(), channel()) -> channel()). -sent(Bytes, Channel) -> - ensure_timer(stats_timer, maybe_gc_and_check_oom(Bytes, Channel)). - -spec(handle_out(term(), channel()) -> {ok, channel()} | {ok, output(), channel()} @@ -578,8 +535,7 @@ handle_out({publish, Publishes}, Channel) when is_list(Publishes) -> {ok, _Ch} -> Acc end end, [], Publishes), - NChannel = inc_pub_stats(publish_out, length(Packets), Channel), - {ok, {outgoing, lists:reverse(Packets)}, NChannel}; + {ok, {outgoing, lists:reverse(Packets)}, Channel}; %% Ignore loop deliver handle_out({publish, _PacketId, #message{from = ClientId, @@ -635,16 +591,16 @@ handle_out(connack, {ReasonCode, _ConnPkt}, Channel = #channel{conninfo = ConnIn shutdown(Reason, ?CONNACK_PACKET(ReasonCode1), Channel); handle_out(puback, {PacketId, ReasonCode}, Channel) -> - {ok, ?PUBACK_PACKET(PacketId, ReasonCode), inc_pub_stats(puback_out, Channel)}; + {ok, ?PUBACK_PACKET(PacketId, ReasonCode), Channel}; handle_out(pubrec, {PacketId, ReasonCode}, Channel) -> - {ok, ?PUBREC_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrec_out, Channel)}; + {ok, ?PUBREC_PACKET(PacketId, ReasonCode), Channel}; handle_out(pubrel, {PacketId, ReasonCode}, Channel) -> - {ok, ?PUBREL_PACKET(PacketId, ReasonCode), inc_pub_stats(pubrel_out, Channel)}; + {ok, ?PUBREL_PACKET(PacketId, ReasonCode), Channel}; handle_out(pubcomp, {PacketId, ReasonCode}, Channel) -> - {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), inc_pub_stats(pubcomp_out, Channel)}; + {ok, ?PUBCOMP_PACKET(PacketId, ReasonCode), Channel}; handle_out(suback, {PacketId, ReasonCodes}, Channel = #channel{conninfo = #{proto_ver := ?MQTT_PROTO_V5}}) -> @@ -747,11 +703,6 @@ handle_info({unsubscribe, TopicFilters}, Channel = #channel{clientinfo = ClientI {_ReasonCodes, NChannel} = process_unsubscribe(TopicFilters1, Channel), {ok, NChannel}; -handle_info({register, Attrs, Stats}, #channel{clientinfo = #{clientid := ClientId}}) -> - ok = emqx_cm:register_channel(ClientId), - emqx_cm:set_chan_attrs(ClientId, Attrs), - emqx_cm:set_chan_stats(ClientId, Stats); - handle_info({sock_closed, _Reason}, Channel = #channel{conn_state = disconnected}) -> {ok, Channel}; @@ -788,12 +739,6 @@ handle_info(Info, Channel) -> -> {ok, channel()} | {ok, Result :: term(), channel()} | {shutdown, Reason :: term(), channel()}). -handle_timeout(TRef, {emit_stats, Stats}, - Channel = #channel{clientinfo = #{clientid := ClientId}, - timers = #{stats_timer := TRef}}) -> - ok = emqx_cm:set_chan_stats(ClientId, Stats), - {ok, clean_timer(stats_timer, Channel)}; - handle_timeout(TRef, {keepalive, StatVal}, Channel = #channel{keepalive = Keepalive, timers = #{alive_timer := TRef}}) -> @@ -873,8 +818,6 @@ reset_timer(Name, Time, Channel) -> clean_timer(Name, Channel = #channel{timers = Timers}) -> Channel#channel{timers = maps:remove(Name, Timers)}. -interval(stats_timer, #channel{clientinfo = #{zone := Zone}}) -> - emqx_zone:get_env(Zone, idle_timeout, 30000); interval(alive_timer, #channel{keepalive = KeepAlive}) -> emqx_keepalive:info(interval, KeepAlive); interval(retry_timer, #channel{session = Session}) -> @@ -912,6 +855,7 @@ publish_will_msg(undefined) -> publish_will_msg(Msg) -> emqx_broker:publish(Msg). + %% @doc Enrich MQTT Connect Info. enrich_conninfo(#mqtt_packet_connect{ proto_name = ProtoName, @@ -1171,7 +1115,7 @@ ensure_keepalive(_AckProps, Channel = #channel{conninfo = ConnInfo}) -> ensure_keepalive_timer(0, Channel) -> Channel; ensure_keepalive_timer(Interval, Channel = #channel{clientinfo = #{zone := Zone}}) -> - Backoff = emqx_zone:get_env(Zone, keepalive_backoff, 0.75), + Backoff = emqx_zone:keepalive_backoff(Zone), Keepalive = emqx_keepalive:init(round(timer:seconds(Interval) * Backoff)), ensure_timer(alive_timer, Channel#channel{keepalive = Keepalive}). @@ -1197,19 +1141,6 @@ is_acl_enabled(#{zone := Zone, is_superuser := IsSuperuser}) -> parse_topic_filters(TopicFilters) -> lists:map(fun emqx_topic:parse/1, TopicFilters). -%%-------------------------------------------------------------------- -%% Maybe GC and Check OOM -%%-------------------------------------------------------------------- - -maybe_gc_and_check_oom(_Oct, Channel = #channel{gc_state = undefined}) -> - Channel; -maybe_gc_and_check_oom(Oct, Channel = #channel{clientinfo = #{zone := Zone}, - gc_state = GCSt}) -> - {IsGC, GCSt1} = emqx_gc:run(1, Oct, GCSt), - IsGC andalso emqx_metrics:inc('channel.gc.cnt'), - IsGC andalso emqx_zone:check_oom(Zone, fun(Shutdown) -> self() ! Shutdown end), - Channel#channel{gc_state = GCSt1}. - %%-------------------------------------------------------------------- %% Helper functions %%-------------------------------------------------------------------- From d256387cee336896290140a87553f462c95c8685 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:09:45 +0800 Subject: [PATCH 25/40] Ensure the 'inc_sent/1', 'inc_recv/1' to return 'ok' --- src/emqx_metrics.erl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/emqx_metrics.erl b/src/emqx_metrics.erl index db42cd1e8..14c812703 100644 --- a/src/emqx_metrics.erl +++ b/src/emqx_metrics.erl @@ -281,7 +281,7 @@ do_inc_recv(?PUBLISH_PACKET(QoS, _PktId)) -> ?QOS_0 -> inc('messages.qos0.received'); ?QOS_1 -> inc('messages.qos1.received'); ?QOS_2 -> inc('messages.qos2.received'); - _ -> ignore + _ -> ok end, inc('packets.publish.received'); do_inc_recv(?PACKET(?PUBACK)) -> @@ -302,13 +302,12 @@ do_inc_recv(?PACKET(?DISCONNECT)) -> inc('packets.disconnect.received'); do_inc_recv(?PACKET(?AUTH)) -> inc('packets.auth.received'); -do_inc_recv(_Packet) -> - ignore. +do_inc_recv(_Packet) -> ok. %% @doc Inc packets sent. Will not count $SYS PUBLISH. --spec(inc_sent(emqx_types:packet()) -> ok | ignore). +-spec(inc_sent(emqx_types:packet()) -> ok). inc_sent(?PUBLISH_PACKET(_QoS, <<"$SYS/", _/binary>>, _, _)) -> - ignore; + ok; inc_sent(Packet) -> inc('packets.sent'), do_inc_sent(Packet). @@ -349,8 +348,7 @@ do_inc_sent(?PACKET(?DISCONNECT)) -> inc('packets.disconnect.sent'); do_inc_sent(?PACKET(?AUTH)) -> inc('packets.auth.sent'); -do_inc_sent(_Packet) -> - ignore. +do_inc_sent(_Packet) -> ok. %%-------------------------------------------------------------------- %% gen_server callbacks From 6f30dca4ba2a0c40ecc4ecc28e3b46d0ea90aabe Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:10:26 +0800 Subject: [PATCH 26/40] Add more option APIs --- src/emqx_zone.erl | 104 +++++++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 28 deletions(-) diff --git a/src/emqx_zone.erl b/src/emqx_zone.erl index 90d56eb0e..386b71af7 100644 --- a/src/emqx_zone.erl +++ b/src/emqx_zone.erl @@ -25,27 +25,58 @@ -logger_header("[Zone]"). +-compile({inline, + [ idle_timeout/1 + , publish_limit/1 + , mqtt_frame_options/1 + , mqtt_strict_mode/1 + , max_packet_size/1 + , mountpoint/1 + , use_username_as_clientid/1 + , stats_timer/1 + , enable_stats/1 + , enable_acl/1 + , enable_ban/1 + , enable_flapping_detect/1 + , ignore_loop_deliver/1 + , server_keepalive/1 + , keepalive_backoff/1 + , max_inflight/1 + , session_expiry_interval/1 + , force_gc_policy/1 + , force_shutdown_policy/1 + ]}). + %% APIs -export([start_link/0, stop/0]). --export([ frame_options/1 +%% Zone Option API +-export([ idle_timeout/1 + , publish_limit/1 + , mqtt_frame_options/1 , mqtt_strict_mode/1 , max_packet_size/1 + , mountpoint/1 , use_username_as_clientid/1 + , stats_timer/1 , enable_stats/1 , enable_acl/1 , enable_ban/1 , enable_flapping_detect/1 , ignore_loop_deliver/1 , server_keepalive/1 + , keepalive_backoff/1 , max_inflight/1 , session_expiry_interval/1 , force_gc_policy/1 , force_shutdown_policy/1 ]). --export([check_oom/2]). +-export([ init_gc_state/1 + , oom_policy/1 + ]). +%% Zone API -export([ get_env/2 , get_env/3 , set_env/3 @@ -63,27 +94,46 @@ , code_change/3 ]). --export_type([zone/0]). +-import(emqx_misc, [maybe_apply/2]). -%% dummy state --record(state, {}). +-export_type([zone/0]). -type(zone() :: atom()). -define(TAB, ?MODULE). -define(SERVER, ?MODULE). +-define(DEFAULT_IDLE_TIMEOUT, 30000). -define(KEY(Zone, Key), {?MODULE, Zone, Key}). -%%-------------------------------------------------------------------- -%% APIs -%%-------------------------------------------------------------------- - -spec(start_link() -> startlink_ret()). start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). --spec(frame_options(zone()) -> emqx_frame:options()). -frame_options(Zone) -> +-spec(stop() -> ok). +stop() -> + gen_server:stop(?SERVER). + +-spec(init_gc_state(zone()) -> emqx_gc:gc_state()). +init_gc_state(Zone) -> + maybe_apply(fun emqx_gc:init/1, force_gc_policy(Zone)). + +-spec(oom_policy(zone()) -> emqx_types:oom_policy()). +oom_policy(Zone) -> force_shutdown_policy(Zone). + +%%-------------------------------------------------------------------- +%% Zone Options API +%%-------------------------------------------------------------------- + +-spec(idle_timeout(zone()) -> pos_integer()). +idle_timeout(Zone) -> + get_env(Zone, idle_timeout, ?DEFAULT_IDLE_TIMEOUT). + +-spec(publish_limit(zone()) -> maybe(esockd_rate_limit:config())). +publish_limit(Zone) -> + get_env(Zone, publish_limit). + +-spec(mqtt_frame_options(zone()) -> emqx_frame:options()). +mqtt_frame_options(Zone) -> #{strict_mode => mqtt_strict_mode(Zone), max_size => max_packet_size(Zone) }. @@ -96,10 +146,17 @@ mqtt_strict_mode(Zone) -> max_packet_size(Zone) -> get_env(Zone, max_packet_size, ?MAX_PACKET_SIZE). +-spec(mountpoint(zone()) -> maybe(emqx_mountpoint:mountpoint())). +mountpoint(Zone) -> get_env(Zone, mountpoint). + -spec(use_username_as_clientid(zone()) -> boolean()). use_username_as_clientid(Zone) -> get_env(Zone, use_username_as_clientid, false). +-spec(stats_timer(zone()) -> undefined | disabled). +stats_timer(Zone) -> + case enable_stats(Zone) of true -> undefined; false -> disabled end. + -spec(enable_stats(zone()) -> boolean()). enable_stats(Zone) -> get_env(Zone, enable_stats, true). @@ -124,6 +181,10 @@ ignore_loop_deliver(Zone) -> server_keepalive(Zone) -> get_env(Zone, server_keepalive). +-spec(keepalive_backoff(zone()) -> float()). +keepalive_backoff(Zone) -> + get_env(Zone, keepalive_backoff, 0.75). + -spec(max_inflight(zone()) -> 0..65535). max_inflight(Zone) -> get_env(Zone, max_inflight, 65535). @@ -140,18 +201,9 @@ force_gc_policy(Zone) -> force_shutdown_policy(Zone) -> get_env(Zone, force_shutdown_policy). --spec(check_oom(zone(), fun()) -> ok | term()). -check_oom(Zone, Action) -> - case emqx_zone:force_shutdown_policy(Zone) of - undefined -> ok; - Policy -> do_check_oom(emqx_oom:init(Policy), Action) - end. - -do_check_oom(OomPolicy, Action) -> - case emqx_oom:check(OomPolicy) of - ok -> ok; - Shutdown -> Action(Shutdown) - end. +%%-------------------------------------------------------------------- +%% APIs +%%-------------------------------------------------------------------- -spec(get_env(maybe(zone()), atom()) -> maybe(term())). get_env(undefined, Key) -> emqx:get_env(Key); @@ -179,17 +231,13 @@ unset_env(Zone, Key) -> force_reload() -> gen_server:call(?SERVER, force_reload). --spec(stop() -> ok). -stop() -> - gen_server:stop(?SERVER, normal, infinity). - %%-------------------------------------------------------------------- %% gen_server callbacks %%-------------------------------------------------------------------- init([]) -> _ = do_reload(), - {ok, #state{}}. + {ok, #{}}. handle_call(force_reload, _From, State) -> _ = do_reload(), From 2b1b58fc66f3e31893e809e51fc074f550fb65e4 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 1 Nov 2019 08:10:58 +0800 Subject: [PATCH 27/40] Add the new 'emqx_limiter' module --- src/emqx_cm.erl | 3 +- src/emqx_connection.erl | 390 +++++++++++++++++++++---------------- src/emqx_gc.erl | 16 +- src/emqx_limiter.erl | 73 +++++++ src/emqx_misc.erl | 114 +++++++---- src/emqx_pd.erl | 8 +- src/emqx_types.erl | 6 + src/emqx_ws_connection.erl | 286 ++++++++++++++++++++------- 8 files changed, 611 insertions(+), 285 deletions(-) create mode 100644 src/emqx_limiter.erl diff --git a/src/emqx_cm.erl b/src/emqx_cm.erl index d61b56b44..aaa79e78b 100644 --- a/src/emqx_cm.erl +++ b/src/emqx_cm.erl @@ -1,4 +1,4 @@ -%%-------------------------------------------------------------------- +%%------------------------------------------------------------------- %% Copyright (c) 2019 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); @@ -93,7 +93,6 @@ start_link() -> %%-------------------------------------------------------------------- %% @doc Register a channel. -%% Channel will be unregistered automatically when the channel process dies -spec(register_channel(emqx_types:clientid()) -> ok). register_channel(ClientId) when is_binary(ClientId) -> register_channel(ClientId, self()). diff --git a/src/emqx_connection.erl b/src/emqx_connection.erl index b557d42b7..f8d984e4b 100644 --- a/src/emqx_connection.erl +++ b/src/emqx_connection.erl @@ -14,7 +14,7 @@ %% limitations under the License. %%-------------------------------------------------------------------- -%% MQTT/TCP Connection +%% MQTT/TCP|TLS Connection -module(emqx_connection). -include("emqx.hrl"). @@ -40,7 +40,7 @@ -export([call/2]). -%% callback +%% Callback -export([init/4]). %% Sys callbacks @@ -50,12 +50,15 @@ , system_get_state/1 ]). -%% Internal callbacks --export([wakeup_from_hib/2]). +%% Internal callback +-export([wakeup_from_hib/3]). + +-import(emqx_misc, + [ maybe_apply/2 + , start_timer/2 + ]). -record(state, { - %% Parent - parent :: pid(), %% TCP/TLS Transport transport :: esockd:transport(), %% TCP/TLS Socket @@ -64,34 +67,37 @@ peername :: emqx_types:peername(), %% Sockname of the connection sockname :: emqx_types:peername(), - %% Sock state + %% Sock State sockstate :: emqx_types:sockstate(), %% The {active, N} option active_n :: pos_integer(), - %% Publish Limit - pub_limit :: maybe(esockd_rate_limit:bucket()), - %% Rate Limit - rate_limit :: maybe(esockd_rate_limit:bucket()), + %% Limiter + limiter :: maybe(emqx_limiter:limiter()), %% Limit Timer limit_timer :: maybe(reference()), - %% Parser State + %% Parse State parse_state :: emqx_frame:parse_state(), %% Serialize function serialize :: emqx_frame:serialize_fun(), %% Channel State channel :: emqx_channel:channel(), - %% Idle timer - idle_timer :: reference() + %% GC State + gc_state :: maybe(emqx_gc:gc_state()), + %% Stats Timer + stats_timer :: disabled | maybe(reference()), + %% Idle Timer + idle_timer :: maybe(reference()) }). -type(state() :: #state{}). -define(ACTIVE_N, 100). --define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, - pub_limit, rate_limit]). +-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, limiter]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt, send_pend]). +-define(ENABLED(X), (X =/= undefined)). + -spec(start_link(esockd:transport(), esockd:socket(), proplists:proplist()) -> {ok, pid()}). start_link(Transport, Socket, Options) -> @@ -123,13 +129,8 @@ info(sockstate, #state{sockstate = SockSt}) -> SockSt; info(active_n, #state{active_n = ActiveN}) -> ActiveN; -info(pub_limit, #state{pub_limit = PubLimit}) -> - limit_info(PubLimit); -info(rate_limit, #state{rate_limit = RateLimit}) -> - limit_info(RateLimit). - -limit_info(Limit) -> - emqx_misc:maybe_apply(fun esockd_rate_limit:info/1, Limit). +info(limiter, #state{limiter = Limiter}) -> + maybe_apply(fun emqx_limiter:info/1, Limiter). %% @doc Get stats of the connection/channel. -spec(stats(pid()|state()) -> emqx_types:stats()). @@ -147,6 +148,13 @@ stats(#state{transport = Transport, ProcStats = emqx_misc:proc_stats(), lists:append([SockStats, ConnStats, ChanStats, ProcStats]). +attrs(#state{active_n = ActiveN, sockstate = SockSt, channel = Channel}) -> + SockAttrs = #{active_n => ActiveN, + sockstate => SockSt + }, + ChanAttrs = emqx_channel:attrs(Channel), + maps:merge(ChanAttrs, #{sockinfo => SockAttrs}). + call(Pid, Req) -> gen_server:call(Pid, Req, infinity). @@ -169,7 +177,6 @@ init(Parent, Transport, RawSocket, Options) -> do_init(Parent, Transport, Socket, Options) -> {ok, Peername} = Transport:ensure_ok_or_exit(peername, [Socket]), {ok, Sockname} = Transport:ensure_ok_or_exit(sockname, [Socket]), - emqx_logger:set_metadata_peername(esockd_net:format(Peername)), Peercert = Transport:ensure_ok_or_exit(peercert, [Socket]), ConnInfo = #{socktype => Transport:type(Socket), peername => Peername, @@ -179,42 +186,39 @@ do_init(Parent, Transport, Socket, Options) -> }, Zone = proplists:get_value(zone, Options), ActiveN = proplists:get_value(active_n, Options, ?ACTIVE_N), - PubLimit = init_limiter(emqx_zone:get_env(Zone, publish_limit)), - RateLimit = init_limiter(proplists:get_value(rate_limit, Options)), - FrameOpts = emqx_zone:frame_options(Zone), + Limiter = emqx_limiter:init(Options), + FrameOpts = emqx_zone:mqtt_frame_options(Zone), ParseState = emqx_frame:initial_parse_state(FrameOpts), Serialize = emqx_frame:serialize_fun(), Channel = emqx_channel:init(ConnInfo, Options), - IdleTimout = emqx_zone:get_env(Zone, idle_timeout, 30000), - IdleTimer = emqx_misc:start_timer(IdleTimout, idle_timeout), - HibAfterTimeout = emqx_zone:get_env(Zone, hibernate_after, IdleTimout*2), - State = #state{parent = Parent, - transport = Transport, + GcState = emqx_zone:init_gc_state(Zone), + StatsTimer = emqx_zone:stats_timer(Zone), + IdleTimeout = emqx_zone:idle_timeout(Zone), + IdleTimer = start_timer(IdleTimeout, idle_timeout), + emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)), + emqx_logger:set_metadata_peername(esockd_net:format(Peername)), + State = #state{transport = Transport, socket = Socket, peername = Peername, sockname = Sockname, sockstate = idle, active_n = ActiveN, - pub_limit = PubLimit, - rate_limit = RateLimit, + limiter = Limiter, parse_state = ParseState, serialize = Serialize, channel = Channel, + gc_state = GcState, + stats_timer = StatsTimer, idle_timer = IdleTimer }, case activate_socket(State) of {ok, NState} -> - hibernate(NState, #{hibernate_after => HibAfterTimeout}); + hibernate(Parent, NState, #{idle_timeout => IdleTimeout}); {error, Reason} -> ok = Transport:fast_close(Socket), exit_on_sock_error(Reason) end. --compile({inline, [init_limiter/1]}). -init_limiter(undefined) -> undefined; -init_limiter({Rate, Burst}) -> - esockd_rate_limit:new(Rate, Burst). - exit_on_sock_error(Reason) when Reason =:= einval; Reason =:= enotconn; Reason =:= closed -> @@ -227,8 +231,7 @@ exit_on_sock_error(Reason) -> %%-------------------------------------------------------------------- %% Recv Loop -recvloop(State = #state{parent = Parent}, - Options = #{hibernate_after := HibAfterTimeout}) -> +recvloop(Parent, State, Options = #{idle_timeout := IdleTimeout}) -> receive {system, From, Request} -> sys:handle_system_msg(Request, From, Parent, @@ -236,33 +239,49 @@ recvloop(State = #state{parent = Parent}, {'EXIT', Parent, Reason} -> terminate(Reason, State); Msg -> - process_msg([Msg], State, Options) + NState = ensure_stats_timer(IdleTimeout, State), + process_msg([Msg], Parent, NState, Options) after - HibAfterTimeout -> - hibernate(State, Options) + IdleTimeout -> + NState = cancel_stats_timer(State), + hibernate(Parent, NState, Options) end. -hibernate(State, Options) -> - proc_lib:hibernate(?MODULE, wakeup_from_hib, [State, Options]). +hibernate(Parent, State, Options) -> + proc_lib:hibernate(?MODULE, wakeup_from_hib, [Parent, State, Options]). -wakeup_from_hib(State, Options) -> +wakeup_from_hib(Parent, State, Options) -> %% Maybe do something later here. - recvloop(State, Options). + recvloop(Parent, State, Options). + +%%-------------------------------------------------------------------- +%% Ensure/cancel stats timer + +-compile({inline, [ensure_stats_timer/2]}). +ensure_stats_timer(Timeout, State = #state{stats_timer = undefined}) -> + State#state{stats_timer = start_timer(Timeout, emit_stats)}; +ensure_stats_timer(_Timeout, State) -> State. + +-compile({inline, [cancel_stats_timer/1]}). +cancel_stats_timer(State = #state{stats_timer = TRef}) when is_reference(TRef) -> + ok = emqx_misc:cancel_timer(TRef), + State#state{stats_timer = undefined}; +cancel_stats_timer(State) -> State. %%-------------------------------------------------------------------- %% Process next Msg -process_msg([], State, Options) -> - recvloop(State, Options); +process_msg([], Parent, State, Options) -> + recvloop(Parent, State, Options); -process_msg([Msg|More], State, Options) -> +process_msg([Msg|More], Parent, State, Options) -> case catch handle_msg(Msg, State) of ok -> - process_msg(More, State, Options); + process_msg(More, Parent, State, Options); {ok, NState} -> - process_msg(More, NState, Options); - {ok, NextMsgs, NState} -> - process_msg(append_msg(NextMsgs, More), NState, Options); + process_msg(More, Parent, NState, Options); + {ok, Msgs, NState} -> + process_msg(append_msg(Msgs, More), Parent, NState, Options); {stop, Reason} -> terminate(Reason, State); {stop, Reason, NState} -> @@ -284,14 +303,12 @@ handle_msg({'$gen_call', From, Req}, State) -> stop(Reason, NState) end; -handle_msg({Inet, _Sock, Data}, State = #state{channel = Channel}) - when Inet == tcp; Inet == ssl -> +handle_msg({Inet, _Sock, Data}, State) when Inet == tcp; Inet == ssl -> ?LOG(debug, "RECV ~p", [Data]), Oct = iolist_size(Data), - emqx_pd:update_counter(incoming_bytes, Oct), + emqx_pd:inc_counter(incoming_bytes, Oct), ok = emqx_metrics:inc('bytes.received', Oct), - NChannel = emqx_channel:recvd(Oct, Channel), - parse_incoming(Data, State#state{channel = NChannel}); + parse_incoming(Data, State); handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State = #state{idle_timer = IdleTimer}) -> @@ -302,6 +319,9 @@ handle_msg({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, }, handle_incoming(Packet, NState); +handle_msg({incoming, ?PACKET(?PINGREQ)}, State) -> + handle_outgoing(?PACKET(?PINGRESP), State); + handle_msg({incoming, Packet}, State) -> handle_incoming(Packet, State); @@ -315,30 +335,34 @@ handle_msg({Closed, _Sock}, State) handle_msg({Passive, _Sock}, State) when Passive == tcp_passive; Passive == ssl_passive -> - %% Rate limit here:) - NState = ensure_rate_limit(State), - handle_info(activate_socket, NState); - -%% Rate limit timer expired. -handle_msg(activate_socket, State) -> - NState = State#state{sockstate = idle, - limit_timer = undefined - }, - handle_info(activate_socket, NState); + InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs), + oct => emqx_pd:reset_counter(incoming_bytes) + }, + %% Ensure Rate Limit + NState = ensure_rate_limit(InStats, State), + %% Run GC and Check OOM + NState1 = check_oom(run_gc(InStats, NState)), + handle_info(activate_socket, NState1); handle_msg(Deliver = {deliver, _Topic, _Msg}, State = #state{channel = Channel}) -> - Delivers = emqx_misc:drain_deliver([Deliver]), - Result = emqx_channel:handle_out(Delivers, Channel), - handle_return(Result, State); + Delivers = [Deliver|emqx_misc:drain_deliver()], + Ret = emqx_channel:handle_out(Delivers, Channel), + handle_chan_return(Ret, State); handle_msg({outgoing, Packets}, State) -> - NState = handle_outgoing(Packets, State), - {ok, NState}; + handle_outgoing(Packets, State); -%% something sent -handle_msg({inet_reply, _Sock, ok}, _State) -> - ok; +%% Something sent +handle_msg({inet_reply, _Sock, ok}, State = #state{active_n = ActiveN}) -> + case emqx_pd:get_counter(outgoing_pubs) > ActiveN of + true -> + OutStats = #{cnt => emqx_pd:reset_counter(outgoing_pubs), + oct => emqx_pd:reset_counter(outgoing_bytes) + }, + {ok, check_oom(run_gc(OutStats, State))}; + false -> ok + end; handle_msg({inet_reply, _Sock, {error, Reason}}, State) -> handle_info({sock_error, Reason}, State); @@ -349,7 +373,8 @@ handle_msg({timeout, TRef, TMsg}, State) -> handle_msg(Shutdown = {shutdown, _Reason}, State) -> stop(Shutdown, State); -handle_msg(Msg, State) -> handle_info(Msg, State). +handle_msg(Msg, State) -> + handle_info(Msg, State). %%-------------------------------------------------------------------- %% Terminate @@ -363,8 +388,8 @@ terminate(Reason, State = #state{channel = Channel}) -> %%-------------------------------------------------------------------- %% Sys callbacks -system_continue(_Parent, _Deb, {State, Options}) -> - recvloop(State, Options). +system_continue(Parent, _Deb, {State, Options}) -> + recvloop(Parent, State, Options). system_terminate(Reason, _Parent, _Deb, {State, _}) -> terminate(Reason, State). @@ -392,8 +417,8 @@ handle_call(_From, Req, State = #state{channel = Channel}) -> shutdown(Reason, Reply, State#state{channel = NChannel}); {shutdown, Reason, Reply, OutPacket, NChannel} -> NState = State#state{channel = NChannel}, - NState1 = handle_outgoing(OutPacket, NState), - shutdown(Reason, Reply, NState1) + ok = handle_outgoing(OutPacket, NState), + shutdown(Reason, Reply, NState) end. %%-------------------------------------------------------------------- @@ -402,8 +427,18 @@ handle_call(_From, Req, State = #state{channel = Channel}) -> handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) -> shutdown(idle_timeout, State); -handle_timeout(TRef, emit_stats, State) -> - handle_timeout(TRef, {emit_stats, stats(State)}, State); +handle_timeout(TRef, limit_timeout, State = #state{limit_timer = TRef}) -> + NState = State#state{sockstate = idle, + limit_timer = undefined + }, + handle_info(activate_socket, NState); + +handle_timeout(TRef, emit_stats, State = #state{stats_timer = TRef, + channel = Channel}) -> + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), + (ClientId =/= undefined) andalso + emqx_cm:set_chan_stats(ClientId, stats(State)), + {ok, State#state{stats_timer = undefined}}; handle_timeout(TRef, keepalive, State = #state{transport = Transport, socket = Socket}) -> @@ -415,7 +450,8 @@ handle_timeout(TRef, keepalive, State = #state{transport = Transport, end; handle_timeout(TRef, Msg, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State). + Ret = emqx_channel:handle_timeout(TRef, Msg, Channel), + handle_chan_return(Ret, State). %%-------------------------------------------------------------------- %% Parse incoming data @@ -450,30 +486,30 @@ next_incoming_msgs(Packets) -> %%-------------------------------------------------------------------- %% Handle incoming packet -handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) -> - _ = inc_incoming_stats(Type), - ok = emqx_metrics:inc_recv(Packet), +handle_incoming(Packet, State = #state{channel = Channel}) + when is_record(Packet, mqtt_packet) -> + ok = inc_incoming_stats(Packet), ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), - handle_return(emqx_channel:handle_in(Packet, Channel), State); + handle_chan_return(emqx_channel:handle_in(Packet, Channel), State); handle_incoming(FrameError, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_in(FrameError, Channel), State). + handle_chan_return(emqx_channel:handle_in(FrameError, Channel), State). %%-------------------------------------------------------------------- %% Handle channel return -handle_return(ok, State) -> +handle_chan_return(ok, State) -> {ok, State}; -handle_return({ok, NChannel}, State) -> +handle_chan_return({ok, NChannel}, State) -> {ok, State#state{channel = NChannel}}; -handle_return({ok, Replies, NChannel}, State) -> +handle_chan_return({ok, Replies, NChannel}, State) -> {ok, next_msgs(Replies), State#state{channel = NChannel}}; -handle_return({shutdown, Reason, NChannel}, State) -> +handle_chan_return({shutdown, Reason, NChannel}, State) -> shutdown(Reason, State#state{channel = NChannel}); -handle_return({shutdown, Reason, OutPacket, NChannel}, State) -> +handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) -> NState = State#state{channel = NChannel}, - NState1 = handle_outgoing(OutPacket, NState), - shutdown(Reason, NState1). + ok = handle_outgoing(OutPacket, NState), + shutdown(Reason, NState). %%-------------------------------------------------------------------- %% Handle outgoing packets @@ -485,14 +521,13 @@ handle_outgoing(Packet, State) -> send((serialize_and_inc_stats_fun(State))(Packet), State). serialize_and_inc_stats_fun(#state{serialize = Serialize}) -> - fun(Packet = ?PACKET(Type)) -> + fun(Packet) -> case Serialize(Packet) of <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!", [emqx_packet:format(Packet)]), <<>>; - Data -> _ = inc_outgoing_stats(Type), - _ = emqx_metrics:inc_sent(Packet), - ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + ok = inc_outgoing_stats(Packet), Data end end. @@ -500,52 +535,52 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) -> %%-------------------------------------------------------------------- %% Send data -send(IoData, State = #state{transport = Transport, - socket = Socket, - channel = Channel}) -> +-spec(send(iodata(), state()) -> ok). +send(IoData, #state{transport = Transport, socket = Socket}) -> Oct = iolist_size(IoData), ok = emqx_metrics:inc('bytes.sent', Oct), + emqx_pd:inc_counter(outgoing_bytes, Oct), case Transport:async_send(Socket, IoData) of - ok -> - State#state{channel = emqx_channel:sent(Oct, Channel)}; + ok -> ok; Error = {error, _Reason} -> - %% Simulate an inet_reply to postpone handling the error - self() ! {inet_reply, Socket, Error}, State + %% Send an inet_reply to postpone handling the error + self() ! {inet_reply, Socket, Error}, + ok end. %%-------------------------------------------------------------------- %% Handle Info -handle_info({connack, ConnAck}, State = #state{active_n = ActiveN, - sockstate = SockSt, - channel = Channel}) -> - NState = handle_outgoing(ConnAck, State), - ChanAttrs = emqx_channel:attrs(Channel), - SockAttrs = #{active_n => ActiveN, - sockstate => SockSt - }, - Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), - handle_info({register, Attrs, stats(State)}, NState); +handle_info({connack, ConnAck}, State = #state{channel = Channel}) -> + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), + ok = emqx_cm:register_channel(ClientId), + ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)), + ok = emqx_cm:set_chan_stats(ClientId, stats(State)), + ok = handle_outgoing(ConnAck, State); -handle_info({enter, disconnected}, State = #state{active_n = ActiveN, - sockstate = SockSt, - channel = Channel}) -> - ChanAttrs = emqx_channel:attrs(Channel), - SockAttrs = #{active_n => ActiveN, - sockstate => SockSt - }, - Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), - handle_info({register, Attrs, stats(State)}, State); +handle_info({enter, disconnected}, State = #state{channel = Channel}) -> + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), + emqx_cm:set_chan_attrs(ClientId, attrs(State)), + emqx_cm:set_chan_stats(ClientId, stats(State)); -handle_info(activate_socket, State) -> +handle_info(activate_socket, State = #state{sockstate = OldSst}) -> case activate_socket(State) of - {ok, NState} -> {ok, NState}; + {ok, NState = #state{sockstate = NewSst}} -> + if OldSst =/= NewSst -> + {ok, {event, sockstate_changed}, NState}; + true -> {ok, NState} + end; {error, Reason} -> handle_info({sock_error, Reason}, State) end; +handle_info({event, sockstate_changed}, State = #state{channel = Channel}) -> + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), + ClientId =/= undefined andalso emqx_cm:set_chan_attrs(ClientId, attrs(State)); + %%TODO: this is not right -handle_info({sock_error, _Reason}, #state{sockstate = closed}) -> ok; +handle_info({sock_error, _Reason}, #state{sockstate = closed}) -> + ok; handle_info({sock_error, Reason}, State) -> ?LOG(debug, "Socket error: ~p", [Reason]), handle_info({sock_closed, Reason}, close_socket(State)); @@ -560,7 +595,45 @@ handle_info({close, Reason}, State) -> handle_info({sock_closed, Reason}, close_socket(State)); handle_info(Info, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_info(Info, Channel), State). + handle_chan_return(emqx_channel:handle_info(Info, Channel), State). + +%%-------------------------------------------------------------------- +%% Ensure rate limit + +ensure_rate_limit(Stats, State = #state{limiter = Limiter}) -> + case ?ENABLED(limiter) andalso emqx_limiter:check(Stats, Limiter) of + false -> State; + {ok, Limiter1} -> + State#state{limiter = Limiter1}; + {pause, Time, Limiter1} -> + ?LOG(debug, "Pause ~pms due to rate limit", [Time]), + TRef = start_timer(Time, limit_timeout), + State#state{sockstate = blocked, + limiter = Limiter1, + limit_timer = TRef + } + end. + +%%-------------------------------------------------------------------- +%% Run GC and Check OOM + +run_gc(Stats, State = #state{gc_state = GcSt}) -> + case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of + false -> State; + {IsGC, GcSt1} -> + IsGC andalso emqx_metrics:inc('channel.gc.cnt'), + State#state{gc_state = GcSt1} + end. + +check_oom(State = #state{channel = Channel}) -> + #{zone := Zone} = emqx_channel:info(clientinfo, Channel), + OomPolicy = emqx_zone:oom_policy(Zone), + case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of + Shutdown = {shutdown, _Reason} -> + erlang:send(self(), Shutdown); + _Other -> ok + end, + State. %%-------------------------------------------------------------------- %% Activate Socket @@ -587,48 +660,30 @@ close_socket(State = #state{transport = Transport, socket = Socket}) -> ok = Transport:fast_close(Socket), State#state{sockstate = closed}. -%%-------------------------------------------------------------------- -%% Ensure rate limit - --define(ENABLED(Rl), (Rl =/= undefined)). - -ensure_rate_limit(State = #state{rate_limit = Rl, pub_limit = Pl}) -> - Pubs = emqx_pd:reset_counter(incoming_pubs), - Bytes = emqx_pd:reset_counter(incoming_bytes), - Limiters = [{Pl, #state.pub_limit, Pubs} || ?ENABLED(Pl)] ++ - [{Rl, #state.rate_limit, Bytes} || ?ENABLED(Rl)], - ensure_rate_limit(Limiters, State). - -ensure_rate_limit([], State) -> - State; -ensure_rate_limit([{Rl, Pos, Cnt}|Limiters], State) -> - case esockd_rate_limit:check(Cnt, Rl) of - {0, Rl1} -> - ensure_rate_limit(Limiters, setelement(Pos, State, Rl1)); - {Pause, Rl1} -> - ?LOG(debug, "Pause ~pms due to rate limit", [Pause]), - TRef = erlang:send_after(Pause, self(), activate_socket), - NState = State#state{sockstate = blocked, limit_timer = TRef}, - setelement(Pos, NState, Rl1) - end. - %%-------------------------------------------------------------------- %% Inc incoming/outgoing stats -compile({inline, [inc_incoming_stats/1]}). -inc_incoming_stats(Type) when is_integer(Type) -> - emqx_pd:update_counter(recv_pkt, 1), +inc_incoming_stats(Packet = ?PACKET(Type)) -> + emqx_pd:inc_counter(recv_pkt, 1), if Type == ?PUBLISH -> - emqx_pd:update_counter(recv_msg, 1), - emqx_pd:update_counter(incoming_pubs, 1); + emqx_pd:inc_counter(recv_msg, 1), + emqx_pd:inc_counter(incoming_pubs, 1); true -> ok - end. + end, + emqx_metrics:inc_recv(Packet). -compile({inline, [inc_outgoing_stats/1]}). -inc_outgoing_stats(Type) -> - emqx_pd:update_counter(send_pkt, 1), - (Type == ?PUBLISH) andalso emqx_pd:update_counter(send_msg, 1). +inc_outgoing_stats(Packet = ?PACKET(Type)) -> + emqx_pd:inc_counter(send_pkt, 1), + if + Type == ?PUBLISH -> + emqx_pd:inc_counter(send_msg, 1), + emqx_pd:inc_counter(outgoing_pubs, 1); + true -> ok + end, + emqx_metrics:inc_sent(Packet). %%-------------------------------------------------------------------- %% Helper functions @@ -646,13 +701,14 @@ next_msgs(Action) when is_tuple(Action) -> next_msgs(Actions) when is_list(Actions) -> Actions. +-compile({inline, [shutdown/2, shutdown/3]}). shutdown(Reason, State) -> stop({shutdown, Reason}, State). shutdown(Reason, Reply, State) -> stop({shutdown, Reason}, Reply, State). --compile({inline, [stop/2]}). +-compile({inline, [stop/2, stop/3]}). stop(Reason, State) -> {stop, Reason, State}. diff --git a/src/emqx_gc.erl b/src/emqx_gc.erl index 5f939bfe5..8da13912c 100644 --- a/src/emqx_gc.erl +++ b/src/emqx_gc.erl @@ -29,6 +29,7 @@ -include("types.hrl"). -export([ init/1 + , run/2 , run/3 , info/1 , reset/1 @@ -57,21 +58,26 @@ init(#{count := Count, bytes := Bytes}) -> ?GCS(maps:from_list(Cnt ++ Oct)). %% @doc Try to run GC based on reduntions of count or bytes. +-spec(run(#{cnt := pos_integer(), oct := pos_integer()}, gc_state()) + -> {boolean(), gc_state()}). +run(#{cnt := Cnt, oct := Oct}, GcSt) -> + run(Cnt, Oct, GcSt). + -spec(run(pos_integer(), pos_integer(), gc_state()) -> {boolean(), gc_state()}). run(Cnt, Oct, ?GCS(St)) -> - {Res, St1} = run([{cnt, Cnt}, {oct, Oct}], St), + {Res, St1} = do_run([{cnt, Cnt}, {oct, Oct}], St), {Res, ?GCS(St1)}. -run([], St) -> +do_run([], St) -> {false, St}; -run([{K, N}|T], St) -> +do_run([{K, N}|T], St) -> case dec(K, N, St) of {true, St1} -> - true = erlang:garbage_collect(), + erlang:garbage_collect(), {true, do_reset(St1)}; {false, St1} -> - run(T, St1) + do_run(T, St1) end. %% @doc Info of GC state. diff --git a/src/emqx_limiter.erl b/src/emqx_limiter.erl new file mode 100644 index 000000000..a063bcad2 --- /dev/null +++ b/src/emqx_limiter.erl @@ -0,0 +1,73 @@ +%%-------------------------------------------------------------------- +%% 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_limiter). + +-include("types.hrl"). + +-export([init/1, info/1, check/2]). + +-import(emqx_misc, [maybe_apply/2]). + +-record(limiter, { + %% Publish Limit + pub_limit :: maybe(esockd_rate_limit:bucket()), + %% Rate Limit + rate_limit :: maybe(esockd_rate_limit:bucket()) + }). + +-type(limiter() :: #limiter{}). + +-export_type([limiter/0]). + +-define(ENABLED(Rl), (Rl =/= undefined)). + +-spec(init(proplists:proplist()) -> maybe(limiter())). +init(Options) -> + Zone = proplists:get_value(zone, Options), + Pl = emqx_zone:publish_limit(Zone), + Rl = proplists:get_value(rate_limit, Options), + case ?ENABLED(Pl) or ?ENABLED(Rl) of + true -> #limiter{pub_limit = init_limit(Pl), + rate_limit = init_limit(Rl) + }; + false -> undefined + end. + +init_limit(Rl) -> + maybe_apply(fun esockd_rate_limit:new/1, Rl). + +info(#limiter{pub_limit = Pl, rate_limit = Rl}) -> + #{pub_limit => info(Pl), rate_limit => info(Rl)}; + +info(Rl) -> + maybe_apply(fun esockd_rate_limit:info/1, Rl). + +check(#{cnt := Cnt, oct := Oct}, Limiter = #limiter{pub_limit = Pl, + rate_limit = Rl}) -> + do_check([{#limiter.pub_limit, Cnt, Pl} || ?ENABLED(Pl)] ++ + [{#limiter.rate_limit, Oct, Rl} || ?ENABLED(Rl)], Limiter). + +do_check([], Limiter) -> + {ok, Limiter}; +do_check([{Pos, Cnt, Rl}|More], Limiter) -> + case esockd_rate_limit:check(Cnt, Rl) of + {0, Rl1} -> + do_check(More, setelement(Pos, Limiter, Rl1)); + {Pause, Rl1} -> + {pause, Pause, setelement(Pos, Limiter, Rl1)} + end. + diff --git a/src/emqx_misc.erl b/src/emqx_misc.erl index 359bb045f..2debe7ad0 100644 --- a/src/emqx_misc.erl +++ b/src/emqx_misc.erl @@ -16,6 +16,8 @@ -module(emqx_misc). +-compile(inline). + -include("types.hrl"). -include("logger.hrl"). @@ -26,22 +28,20 @@ , start_timer/2 , start_timer/3 , cancel_timer/1 + , drain_deliver/0 + , drain_down/1 + , check_oom/1 + , check_oom/2 + , tune_heap_size/1 , proc_name/2 , proc_stats/0 , proc_stats/1 + , rand_seed/0 + , now_to_secs/1 + , now_to_ms/1 , index_of/2 ]). --export([ drain_deliver/0 - , drain_deliver/1 - , drain_down/1 - ]). - --compile({inline, - [ start_timer/2 - , start_timer/3 - ]}). - %% @doc Merge options -spec(merge_opts(Opts, Opts) -> Opts when Opts :: proplists:proplist()). merge_opts(Defaults, Options) -> @@ -112,6 +112,68 @@ cancel_timer(Timer) when is_reference(Timer) -> end; cancel_timer(_) -> ok. +%% @doc Drain delivers from the channel proc's mailbox. +drain_deliver() -> + drain_deliver([]). + +drain_deliver(Acc) -> + receive + Deliver = {deliver, _Topic, _Msg} -> + drain_deliver([Deliver|Acc]) + after 0 -> + lists:reverse(Acc) + end. + +%% @doc Drain process 'DOWN' events. +-spec(drain_down(pos_integer()) -> list(pid())). +drain_down(Cnt) when Cnt > 0 -> + drain_down(Cnt, []). + +drain_down(0, Acc) -> + lists:reverse(Acc); +drain_down(Cnt, Acc) -> + receive + {'DOWN', _MRef, process, Pid, _Reason} -> + drain_down(Cnt - 1, [Pid|Acc]) + after 0 -> + lists:reverse(Acc) + end. + +%% @doc Check process's mailbox and heapsize against OOM policy, +%% return `ok | {shutdown, Reason}' accordingly. +%% `ok': There is nothing out of the ordinary. +%% `shutdown': Some numbers (message queue length hit the limit), +%% hence shutdown for greater good (system stability). +-spec(check_oom(emqx_types:oom_policy()) -> ok | {shutdown, term()}). +check_oom(Policy) -> + check_oom(self(), Policy). + +-spec(check_oom(pid(), emqx_types:oom_policy()) -> ok | {shutdown, term()}). +check_oom(Pid, #{message_queue_len := MaxQLen, + max_heap_size := MaxHeapSize}) -> + case process_info(Pid, [message_queue_len, total_heap_size]) of + undefined -> ok; + [{message_queue_len, QLen}, {total_heap_size, HeapSize}] -> + do_check_oom([{QLen, MaxQLen, message_queue_too_long}, + {HeapSize, MaxHeapSize, proc_heap_too_large} + ]) + end. + +do_check_oom([]) -> ok; +do_check_oom([{Val, Max, Reason}|Rest]) -> + case is_integer(Max) andalso (0 < Max) andalso (Max < Val) of + true -> {shutdown, Reason}; + false -> do_check_oom(Rest) + end. + +tune_heap_size(#{max_heap_size := MaxHeapSize}) -> + %% If set to zero, the limit is disabled. + erlang:process_flag(max_heap_size, #{size => MaxHeapSize, + kill => false, + error_logger => true + }); +tune_heap_size(undefined) -> ok. + -spec(proc_name(atom(), pos_integer()) -> atom()). proc_name(Mod, Id) -> list_to_atom(lists:concat([Mod, "_", Id])). @@ -132,32 +194,16 @@ proc_stats(Pid) -> [{mailbox_len, Len}|ProcStats] end. -%% @doc Drain delivers from the channel's mailbox. -drain_deliver() -> - drain_deliver([]). +rand_seed() -> + rand:seed(exsplus, erlang:timestamp()). -drain_deliver(Acc) -> - receive - Deliver = {deliver, _Topic, _Msg} -> - drain_deliver([Deliver|Acc]) - after 0 -> - lists:reverse(Acc) - end. +-spec(now_to_secs(erlang:timestamp()) -> pos_integer()). +now_to_secs({MegaSecs, Secs, _MicroSecs}) -> + MegaSecs * 1000000 + Secs. -%% @doc Drain process down events. --spec(drain_down(pos_integer()) -> list(pid())). -drain_down(Cnt) when Cnt > 0 -> - drain_down(Cnt, []). - -drain_down(0, Acc) -> - lists:reverse(Acc); -drain_down(Cnt, Acc) -> - receive - {'DOWN', _MRef, process, Pid, _Reason} -> - drain_down(Cnt - 1, [Pid|Acc]) - after 0 -> - drain_down(0, Acc) - end. +-spec(now_to_ms(erlang:timestamp()) -> pos_integer()). +now_to_ms({MegaSecs, Secs, MicroSecs}) -> + (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). %% lists:index_of/2 index_of(E, L) -> diff --git a/src/emqx_pd.erl b/src/emqx_pd.erl index db33927d0..1bcbcd283 100644 --- a/src/emqx_pd.erl +++ b/src/emqx_pd.erl @@ -21,14 +21,14 @@ -export([ get_counters/1 , get_counter/1 - , update_counter/2 + , inc_counter/2 , reset_counter/1 ]). -compile({inline, [ get_counters/1 , get_counter/1 - , update_counter/2 + , inc_counter/2 , reset_counter/1 ]}). @@ -42,8 +42,8 @@ get_counters(Keys) when is_list(Keys) -> get_counter(Key) -> case get(Key) of undefined -> 0; Cnt -> Cnt end. --spec(update_counter(key(), number()) -> maybe(number())). -update_counter(Key, Inc) -> +-spec(inc_counter(key(), number()) -> maybe(number())). +inc_counter(Key, Inc) -> put(Key, get_counter(Key) + Inc). -spec(reset_counter(key()) -> number()). diff --git a/src/emqx_types.erl b/src/emqx_types.erl index c895393b9..be9f4a558 100644 --- a/src/emqx_types.erl +++ b/src/emqx_types.erl @@ -85,6 +85,8 @@ , stats/0 ]). +-export_type([oom_policy/0]). + -type(ver() :: ?MQTT_PROTO_V3 | ?MQTT_PROTO_V4 | ?MQTT_PROTO_V5). @@ -186,3 +188,7 @@ -type(infos() :: #{atom() => term()}). -type(stats() :: #{atom() => non_neg_integer()|stats()}). +-type(oom_policy() :: #{message_queue_len => non_neg_integer(), + max_heap_size => non_neg_integer() + }). + diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index 4fb39e16e..c5e849c1e 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -45,6 +45,11 @@ , terminate/3 ]). +-import(emqx_misc, + [ maybe_apply/2 + , start_timer/2 + ]). + -record(state, { %% Peername of the ws connection. peername :: emqx_types:peername(), @@ -52,24 +57,41 @@ sockname :: emqx_types:peername(), %% Sock state sockstate :: emqx_types:sockstate(), - %% Parser State + %% Simulate the active_n opt + active_n :: pos_integer(), + %% Limiter + limiter :: emqx_limiter:limiter(), + %% Limit Timer + limit_timer :: maybe(reference()), + %% Parse State parse_state :: emqx_frame:parse_state(), %% Serialize function serialize :: emqx_frame:serialize_fun(), %% Channel channel :: emqx_channel:channel(), + %% GC State + gc_state :: maybe(emqx_gc:gc_state()), %% Out Pending Packets pendings :: list(emqx_types:packet()), + %% Stats Timer + stats_timer :: disabled | maybe(reference()), + %% Idle Timeout + idle_timeout :: timeout(), + %% Idle Timer + idle_timer :: reference(), %% The stop reason stop_reason :: term() }). -type(state() :: #state{}). --define(INFO_KEYS, [socktype, peername, sockname, sockstate]). +-define(ACTIVE_N, 100). +-define(INFO_KEYS, [socktype, peername, sockname, sockstate, active_n, limiter]). -define(SOCK_STATS, [recv_oct, recv_cnt, send_oct, send_cnt]). -define(CONN_STATS, [recv_pkt, recv_msg, send_pkt, send_msg]). +-define(ENABLED(X), (X =/= undefined)). + %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- @@ -92,11 +114,20 @@ info(sockname, #state{sockname = Sockname}) -> Sockname; info(sockstate, #state{sockstate = SockSt}) -> SockSt; +info(active_n, #state{active_n = ActiveN}) -> + ActiveN; +info(limiter, #state{limiter = Limiter}) -> + maybe_apply(fun emqx_limiter:info/1, Limiter); info(channel, #state{channel = Channel}) -> emqx_channel:info(Channel); info(stop_reason, #state{stop_reason = Reason}) -> Reason. +attrs(State = #state{channel = Channel}) -> + ChanAttrs = emqx_channel:attrs(Channel), + SockAttrs = maps:from_list(info(?INFO_KEYS, State)), + maps:merge(ChanAttrs, #{sockinfo => SockAttrs}). + -spec(stats(pid()|state()) -> emqx_types:stats()). stats(WsPid) when is_pid(WsPid) -> call(WsPid, stats); @@ -128,6 +159,7 @@ call(WsPid, Req) when is_pid(WsPid) -> %%-------------------------------------------------------------------- init(Req, Opts) -> + %% WS Transport Idle Timeout IdleTimeout = proplists:get_value(idle_timeout, Opts, 7200000), DeflateOptions = maps:from_list(proplists:get_value(deflate_options, Opts, [])), MaxFrameSize = case proplists:get_value(max_frame_size, Opts, 0) of @@ -174,29 +206,41 @@ websocket_init([Req, Opts]) -> conn_mod => ?MODULE }, Zone = proplists:get_value(zone, Opts), - FrameOpts = emqx_zone:frame_options(Zone), + ActiveN = proplists:get_value(active_n, Opts, ?ACTIVE_N), + Limiter = emqx_limiter:init(Opts), + FrameOpts = emqx_zone:mqtt_frame_options(Zone), ParseState = emqx_frame:initial_parse_state(FrameOpts), Serialize = emqx_frame:serialize_fun(), Channel = emqx_channel:init(ConnInfo, Opts), + GcState = emqx_zone:init_gc_state(Zone), + StatsTimer = emqx_zone:stats_timer(Zone), + %% MQTT Idle Timeout + IdleTimeout = emqx_zone:idle_timeout(Zone), + IdleTimer = start_timer(IdleTimeout, idle_timeout), + emqx_misc:tune_heap_size(emqx_zone:oom_policy(Zone)), emqx_logger:set_metadata_peername(esockd_net:format(Peername)), - {ok, #state{peername = Peername, - sockname = Sockname, - sockstate = idle, - parse_state = ParseState, - serialize = Serialize, - channel = Channel, - pendings = [] - }}. + {ok, #state{peername = Peername, + sockname = Sockname, + sockstate = running, + active_n = ActiveN, + limiter = Limiter, + parse_state = ParseState, + serialize = Serialize, + channel = Channel, + gc_state = GcState, + pendings = [], + stats_timer = StatsTimer, + idle_timeout = IdleTimeout, + idle_timer = IdleTimer + }, hibernate}. websocket_handle({binary, Data}, State) when is_list(Data) -> websocket_handle({binary, iolist_to_binary(Data)}, State); -websocket_handle({binary, Data}, State = #state{channel = Channel}) -> +websocket_handle({binary, Data}, State) -> ?LOG(debug, "RECV ~p", [Data]), - Oct = iolist_size(Data), - ok = inc_recv_stats(1, Oct), - NChannel = emqx_channel:recvd(Oct, Channel), - parse_incoming(Data, State#state{channel = NChannel}); + ok = inc_recv_stats(1, iolist_size(Data)), + parse_incoming(Data, ensure_stats_timer(State)); %% Pings should be replied with pongs, cowboy does it automatically %% Pongs can be safely ignored. Clause here simply prevents crash. @@ -215,30 +259,43 @@ websocket_info({call, From, Req}, State) -> handle_call(From, Req, State); websocket_info({cast, Msg}, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_info(Msg, Channel), State); + handle_chan_return(emqx_channel:handle_info(Msg, Channel), State); -websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, State) -> +websocket_info({incoming, Packet = ?CONNECT_PACKET(ConnPkt)}, + State = #state{idle_timer = IdleTimer}) -> + ok = emqx_misc:cancel_timer(IdleTimer), Serialize = emqx_frame:serialize_fun(ConnPkt), - NState = State#state{sockstate = running, - serialize = Serialize + NState = State#state{serialize = Serialize, + idle_timer = undefined }, handle_incoming(Packet, NState); +websocket_info({incoming, ?PACKET(?PINGREQ)}, State) -> + reply(?PACKET(?PINGRESP), State); + websocket_info({incoming, Packet}, State) -> handle_incoming(Packet, State); -websocket_info(Deliver = {deliver, _Topic, _Msg}, - State = #state{channel = Channel}) -> - Delivers = emqx_misc:drain_deliver([Deliver]), - Result = emqx_channel:handle_out(Delivers, Channel), - handle_return(Result, State); +websocket_info(rate_limit, State) -> + InStats = #{cnt => emqx_pd:reset_counter(incoming_pubs), + oct => emqx_pd:reset_counter(incoming_bytes) + }, + erlang:send(self(), {check_gc, InStats}), + ensure_rate_limit(InStats, State); -websocket_info({timeout, TRef, keepalive}, State) when is_reference(TRef) -> - RecvOct = emqx_pd:get_counter(recv_oct), - handle_timeout(TRef, {keepalive, RecvOct}, State); +websocket_info({check_gc, Stats}, State) -> + {ok, check_oom(run_gc(Stats, State))}; -websocket_info({timeout, TRef, emit_stats}, State) when is_reference(TRef) -> - handle_timeout(TRef, {emit_stats, stats(State)}, State); +websocket_info({deliver, _Topic, _Msg} = Deliver, State = #state{channel = Channel}) -> + Delivers = [Deliver|emqx_misc:drain_deliver()], + Ret = emqx_channel:handle_out(Delivers, Channel), + handle_chan_return(Ret, State); + +websocket_info({timeout, TRef, limit_timeout}, State = #state{limit_timer = TRef}) -> + NState = State#state{sockstate = running, + limit_timer = undefined + }, + {reply, [{active, true}], NState}; websocket_info({timeout, TRef, Msg}, State) when is_reference(TRef) -> handle_timeout(TRef, Msg, State); @@ -293,27 +350,89 @@ handle_call(From, Req, State = #state{channel = Channel}) -> %% Handle Info handle_info({connack, ConnAck}, State = #state{channel = Channel}) -> - ChanAttrs = emqx_channel:attrs(Channel), - SockAttrs = maps:from_list(info(?INFO_KEYS, State)), - Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), - ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel), + ClientId = emqx_channel:info(clientid, Channel), + ok = emqx_cm:register_channel(ClientId), + ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)), + ok = emqx_cm:set_chan_stats(ClientId, stats(State)), reply(enqueue(ConnAck, State)); handle_info({enter, disconnected}, State = #state{channel = Channel}) -> - ChanAttrs = emqx_channel:attrs(Channel), - SockAttrs = maps:from_list(info(?INFO_KEYS, State)), - Attrs = maps:merge(ChanAttrs, #{sockinfo => SockAttrs}), - ok = emqx_channel:handle_info({register, Attrs, stats(State)}, Channel), + ClientId = emqx_channel:info(clientid, Channel), + emqx_cm:set_chan_attrs(ClientId, attrs(State)), + emqx_cm:set_chan_stats(ClientId, stats(State)), reply(State); handle_info(Info, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_info(Info, Channel), State). + Ret = emqx_channel:handle_info(Info, Channel), + handle_chan_return(Ret, State). %%-------------------------------------------------------------------- %% Handle timeout -handle_timeout(TRef, Msg, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_timeout(TRef, Msg, Channel), State). +handle_timeout(TRef, idle_timeout, State = #state{idle_timer = TRef}) -> + shutdown(idle_timeout, State); + +handle_timeout(TRef, keepalive, State) when is_reference(TRef) -> + RecvOct = emqx_pd:get_counter(recv_oct), + handle_timeout(TRef, {keepalive, RecvOct}, State); + +handle_timeout(TRef, emit_stats, State = #state{channel = Channel, + stats_timer = TRef}) -> + ClientId = emqx_channel:info(clientid, Channel), + (ClientId =/= undefined) andalso emqx_cm:set_chan_stats(ClientId, stats(State)), + reply(State#state{stats_timer = undefined}); + +handle_timeout(TRef, TMsg, State = #state{channel = Channel}) -> + Ret = emqx_channel:handle_timeout(TRef, TMsg, Channel), + handle_chan_return(Ret, State). + +%%-------------------------------------------------------------------- +%% Ensure stats timer + +-compile({inline, [ensure_stats_timer/1]}). +ensure_stats_timer(State = #state{idle_timeout = Timeout, + stats_timer = undefined}) -> + State#state{stats_timer = start_timer(Timeout, emit_stats)}; +ensure_stats_timer(State) -> State. + +%%-------------------------------------------------------------------- +%% Ensure rate limit + +ensure_rate_limit(Stats, State = #state{limiter = Limiter}) -> + case ?ENABLED(Limiter) andalso emqx_limiter:check(Stats, Limiter) of + false -> {ok, State}; + {ok, Limiter1} -> + {ok, State#state{limiter = Limiter1}}; + {pause, Time, Limiter1} -> + ?LOG(debug, "Pause ~pms due to rate limit", [Time]), + TRef = start_timer(Time, limit_timeout), + NState = State#state{sockstate = blocked, + limiter = Limiter1, + limit_timer = TRef + }, + {reply, [{active, false}], NState} + end. + +%%-------------------------------------------------------------------- +%% Run GC and Check OOM + +run_gc(Stats, State = #state{gc_state = GcSt}) -> + case ?ENABLED(GcSt) andalso emqx_gc:run(Stats, GcSt) of + false -> State; + {IsGC, GcSt1} -> + IsGC andalso emqx_metrics:inc('channel.gc.cnt'), + State#state{gc_state = GcSt1} + end. + +check_oom(State = #state{channel = Channel}) -> + #{zone := Zone} = emqx_channel:info(clientinfo, Channel), + OomPolicy = emqx_zone:oom_policy(Zone), + case ?ENABLED(OomPolicy) andalso emqx_misc:check_oom(OomPolicy) of + Shutdown = {shutdown, _Reason} -> + erlang:send(self(), Shutdown); + _Other -> ok + end, + State. %%-------------------------------------------------------------------- %% Parse incoming data @@ -326,7 +445,7 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) -> {more, NParseState} -> {ok, State#state{parse_state = NParseState}}; {ok, Packet, Rest, NParseState} -> - self() ! {incoming, Packet}, + erlang:send(self(), {incoming, Packet}), parse_incoming(Rest, State#state{parse_state = NParseState}) catch error:Reason:Stk -> @@ -337,52 +456,60 @@ parse_incoming(Data, State = #state{parse_state = ParseState}) -> end. %%-------------------------------------------------------------------- -%% Handle incoming packets +%% Handle incoming packet -handle_incoming(Packet = ?PACKET(Type), State = #state{channel = Channel}) -> - _ = inc_incoming_stats(Type), - _ = emqx_metrics:inc_recv(Packet), +handle_incoming(Packet, State = #state{active_n = ActiveN, channel = Channel}) + when is_record(Packet, mqtt_packet) -> ?LOG(debug, "RECV ~s", [emqx_packet:format(Packet)]), - handle_return(emqx_channel:handle_in(Packet, Channel), State); + ok = inc_incoming_stats(Packet), + (emqx_pd:get_counter(incoming_pubs) > ActiveN) + andalso erlang:send(self(), rate_limit), + Ret = emqx_channel:handle_in(Packet, Channel), + handle_chan_return(Ret, State); handle_incoming(FrameError, State = #state{channel = Channel}) -> - handle_return(emqx_channel:handle_in(FrameError, Channel), State). + handle_chan_return(emqx_channel:handle_in(FrameError, Channel), State). %%-------------------------------------------------------------------- %% Handle channel return -handle_return(ok, State) -> +handle_chan_return(ok, State) -> reply(State); -handle_return({ok, NChannel}, State) -> +handle_chan_return({ok, NChannel}, State) -> reply(State#state{channel= NChannel}); -handle_return({ok, Replies, NChannel}, State) -> +handle_chan_return({ok, Replies, NChannel}, State) -> reply(Replies, State#state{channel= NChannel}); -handle_return({shutdown, Reason, NChannel}, State) -> +handle_chan_return({shutdown, Reason, NChannel}, State) -> stop(Reason, State#state{channel = NChannel}); -handle_return({shutdown, Reason, OutPacket, NChannel}, State) -> +handle_chan_return({shutdown, Reason, OutPacket, NChannel}, State) -> NState = State#state{channel = NChannel}, stop(Reason, enqueue(OutPacket, NState)). %%-------------------------------------------------------------------- %% Handle outgoing packets -handle_outgoing(Packets, State = #state{channel = Channel}) -> +handle_outgoing(Packets, State = #state{active_n = ActiveN}) -> IoData = lists:map(serialize_and_inc_stats_fun(State), Packets), Oct = iolist_size(IoData), ok = inc_sent_stats(length(Packets), Oct), - NChannel = emqx_channel:sent(Oct, Channel), - {{binary, IoData}, State#state{channel = NChannel}}. + case emqx_pd:get_counter(outgoing_pubs) > ActiveN of + true -> + OutStats = #{cnt => emqx_pd:reset_counter(outgoing_pubs), + oct => emqx_pd:reset_counter(outgoing_bytes) + }, + erlang:send(self(), {check_gc, OutStats}); + false -> ok + end, + {{binary, IoData}, ensure_stats_timer(State)}. -%% TODO: Duplicated with emqx_channel:serialize_and_inc_stats_fun/1 serialize_and_inc_stats_fun(#state{serialize = Serialize}) -> - fun(Packet = ?PACKET(Type)) -> + fun(Packet) -> case Serialize(Packet) of <<>> -> ?LOG(warning, "~s is discarded due to the frame is too large!", [emqx_packet:format(Packet)]), <<>>; - Data -> _ = inc_outgoing_stats(Type), - _ = emqx_metrics:inc_sent(Packet), - ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + Data -> ?LOG(debug, "SEND ~s", [emqx_packet:format(Packet)]), + ok = inc_outgoing_stats(Packet), Data end end. @@ -398,23 +525,33 @@ serialize_and_inc_stats_fun(#state{serialize = Serialize}) -> ]}). inc_recv_stats(Cnt, Oct) -> - emqx_pd:update_counter(recv_cnt, Cnt), - emqx_pd:update_counter(recv_oct, Oct), + emqx_pd:inc_counter(incoming_bytes, Oct), + emqx_pd:inc_counter(recv_cnt, Cnt), + emqx_pd:inc_counter(recv_oct, Oct), emqx_metrics:inc('bytes.received', Oct). -inc_incoming_stats(Type) -> - emqx_pd:update_counter(recv_pkt, 1), - (Type == ?PUBLISH) - andalso emqx_pd:update_counter(recv_msg, 1). +inc_incoming_stats(Packet = ?PACKET(Type)) -> + emqx_pd:inc_counter(recv_pkt, 1), + if Type == ?PUBLISH -> + emqx_pd:inc_counter(recv_msg, 1), + emqx_pd:inc_counter(incoming_pubs, 1); + true -> ok + end, + emqx_metrics:inc_recv(Packet). -inc_outgoing_stats(Type) -> - emqx_pd:update_counter(send_pkt, 1), - (Type == ?PUBLISH) - andalso emqx_pd:update_counter(send_msg, 1). +inc_outgoing_stats(Packet = ?PACKET(Type)) -> + emqx_pd:inc_counter(send_pkt, 1), + if Type == ?PUBLISH -> + emqx_pd:inc_counter(send_msg, 1), + emqx_pd:inc_counter(outgoing_pubs, 1); + true -> ok + end, + emqx_metrics:inc_sent(Packet). inc_sent_stats(Cnt, Oct) -> - emqx_pd:update_counter(send_cnt, Cnt), - emqx_pd:update_counter(send_oct, Oct), + emqx_pd:inc_counter(outgoing_bytes, Oct), + emqx_pd:inc_counter(send_cnt, Cnt), + emqx_pd:inc_counter(send_oct, Oct), emqx_metrics:inc('bytes.sent', Oct). %%-------------------------------------------------------------------- @@ -451,6 +588,9 @@ enqueue(Packet, State) when is_record(Packet, mqtt_packet) -> enqueue(Packets, State = #state{pendings = Pendings}) -> State#state{pendings = lists:append(Pendings, Packets)}. +shutdown(Reason, State) -> + stop({shutdown, Reason}, State). + stop(Reason, State = #state{pendings = []}) -> {stop, State#state{stop_reason = Reason}}; stop(Reason, State = #state{pendings = Pendings}) -> From 48f944a9c8b479ae2cb9e021e50717c65fb31b62 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Fri, 1 Nov 2019 09:23:45 +0800 Subject: [PATCH 28/40] Add more test cases for emqx_listeners --- test/emqx_listeners_SUITE.erl | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/test/emqx_listeners_SUITE.erl b/test/emqx_listeners_SUITE.erl index a03485017..136b219aa 100644 --- a/test/emqx_listeners_SUITE.erl +++ b/test/emqx_listeners_SUITE.erl @@ -37,6 +37,7 @@ end_per_suite(_Config) -> t_start_stop_listeners(_) -> ok = emqx_listeners:start(), + {error, _} = emqx_listeners:start_listener({ws,{"127.0.0.1", 8083}, []}), ok = emqx_listeners:stop(). t_restart_listeners(_) -> @@ -89,23 +90,4 @@ get_base_dir(Module) -> get_base_dir() -> get_base_dir(?MODULE). - - -t_start_listener(_) -> - error('TODO'). - -t_restart(_) -> - error('TODO'). - -t_restart_listener(_) -> - error('TODO'). - -t_stop_listener(_) -> - error('TODO'). - -t_stop(_) -> - error('TODO'). - -t_start(_) -> - error('TODO'). - + \ No newline at end of file From c5ad674dd78e157e6866680bb122e2890377df19 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Fri, 1 Nov 2019 15:48:28 +0800 Subject: [PATCH 29/40] Add test cases and fix bugs --- src/emqx_broker_helper.erl | 5 + src/emqx_flapping.erl | 4 +- src/emqx_metrics.erl | 2 +- src/emqx_vm.erl | 5 + test/emqx_broker_SUITE.erl | 317 +++++++++++++++--------------- test/emqx_broker_helper_SUITE.erl | 72 +++---- test/emqx_cm_registry_SUITE.erl | 71 ++++--- test/emqx_metrics_SUITE.erl | 78 ++++++-- test/emqx_misc_SUITE.erl | 25 ++- test/emqx_pqueue_SUITE.erl | 246 ++++++++++++----------- test/emqx_vm_SUITE.erl | 47 ++++- 11 files changed, 504 insertions(+), 368 deletions(-) diff --git a/src/emqx_broker_helper.erl b/src/emqx_broker_helper.erl index 34e2de817..3364ffc98 100644 --- a/src/emqx_broker_helper.erl +++ b/src/emqx_broker_helper.erl @@ -43,6 +43,11 @@ , code_change/3 ]). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + -define(HELPER, ?MODULE). -define(SUBID, emqx_subid). -define(SUBMON, emqx_submon). diff --git a/src/emqx_flapping.erl b/src/emqx_flapping.erl index e058f0cda..252a85ab9 100644 --- a/src/emqx_flapping.erl +++ b/src/emqx_flapping.erl @@ -149,9 +149,9 @@ handle_cast({detected, Flapping = #flapping{clientid = ClientId, ets:insert(?FLAPPING_TAB, BannedFlapping); false -> ?LOG(warning, "~s(~s) disconnected ~w times in ~wms", - [ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval]), - ets:delete_object(?FLAPPING_TAB, Flapping) + [ClientId, esockd_net:ntoa(PeerHost), DetectCnt, Interval]) end, + ets:delete_object(?FLAPPING_TAB, Flapping), {noreply, State}; handle_cast(Msg, State) -> diff --git a/src/emqx_metrics.erl b/src/emqx_metrics.erl index db42cd1e8..bf6a3e26d 100644 --- a/src/emqx_metrics.erl +++ b/src/emqx_metrics.erl @@ -368,7 +368,7 @@ init([]) -> Metric = #metric{name = Name, type = Type, idx = reserved_idx(Name)}, true = ets:insert(?TAB, Metric), ok = counters:put(CRef, Idx, 0) - end,?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS ++ ?MQTT_METRICS), + end,?BYTES_METRICS ++ ?PACKET_METRICS ++ ?MESSAGE_METRICS ++ ?CHAN_METRICS ++ ?MQTT_METRICS), {ok, #state{next_idx = ?RESERVED_IDX + 1}, hibernate}. handle_call({create, Type, Name}, _From, State = #state{next_idx = ?MAX_SIZE}) -> diff --git a/src/emqx_vm.erl b/src/emqx_vm.erl index ba07eee4c..2fee25633 100644 --- a/src/emqx_vm.erl +++ b/src/emqx_vm.erl @@ -47,6 +47,11 @@ , get_port_info/1 ]). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + -export([cpu_util/0]). -define(UTIL_ALLOCATORS, [temp_alloc, diff --git a/test/emqx_broker_SUITE.erl b/test/emqx_broker_SUITE.erl index 3be56157a..a03cbb979 100644 --- a/test/emqx_broker_SUITE.erl +++ b/test/emqx_broker_SUITE.erl @@ -29,24 +29,8 @@ all() -> emqx_ct:all(?MODULE). -groups() -> - [{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:boot_modules([router, broker]), + emqx_ct_helpers:boot_modules(all), emqx_ct_helpers:start_apps([]), Config. @@ -57,156 +41,173 @@ end_per_suite(_Config) -> %% PubSub Test %%-------------------------------------------------------------------- -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_broker:unsubscribe(<<"topic">>), - ok = emqx_broker:unsubscribe(<<"topic/1">>), - ok = emqx_broker:unsubscribe(<<"topic/2">>). +t_subscribed(_) -> + emqx_broker:subscribe(<<"topic">>), + ?assertEqual(false, emqx_broker:subscribed(undefined, <<"topic">>)), + ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)), + emqx_broker:unsubscribe(<<"topic">>). -t_publish(_) -> - Msg = emqx_message:make(ct, <<"test/pubsub">>, <<"hello">>), - ok = emqx_broker:subscribe(<<"test/+">>), - timer:sleep(10), - emqx_broker:publish(Msg), - ?assert(receive {deliver, <<"test/+">>, #message{payload = <<"hello">>}} -> true after 100 -> false end). +t_subscribed_2(_) -> + emqx_broker:subscribe(<<"topic">>, <<"clientid">>), + ?assertEqual(true, emqx_broker:subscribed(<<"clientid">>, <<"topic">>)), + ?assertEqual(true, emqx_broker:subscribed(self(), <<"topic">>)), + emqx_broker:unsubscribe(<<"topic">>). -t_dispatch_with_no_sub(_) -> - Msg = emqx_message:make(ct, <<"no_subscribers">>, <<"hello">>), - Delivery = #delivery{sender = self(), message = Msg}, - ?assertEqual([{node(),<<"no_subscribers">>,{error,no_subscribers}}], - emqx_broker:route([{<<"no_subscribers">>, node()}], Delivery)). +t_subopts(_) -> + ?assertEqual(false, emqx_broker:set_subopts(<<"topic">>, #{qos => 1})), + ?assertEqual(undefined, emqx_broker:get_subopts(self(), <<"topic">>)), + ?assertEqual(undefined, emqx_broker:get_subopts(<<"clientid">>, <<"topic">>)), + emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 1}), + ?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)), + ?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(<<"clientid">>,<<"topic">>)), + emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 2}), + ?assertEqual(#{qos => 1, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)), + ?assertEqual(true, emqx_broker:set_subopts(<<"topic">>, #{qos => 2})), + ?assertEqual(#{qos => 2, subid => <<"clientid">>}, emqx_broker:get_subopts(self(), <<"topic">>)), + emqx_broker:unsubscribe(<<"topic">>). -t_pubsub(_) -> - true = emqx:is_running(node()), - Self = self(), - Subscriber = <<"clientId">>, - 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_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_broker:publish( - emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)), +t_topics(_) -> + Topics = [<<"topic">>, <<"topic/1">>, <<"topic/2">>], + ok = emqx_broker:subscribe(lists:nth(1, Topics), <<"clientId">>), + ok = emqx_broker:subscribe(lists:nth(2, Topics), <<"clientId">>), + ok = emqx_broker:subscribe(lists:nth(3, Topics), <<"clientId">>), + Topics1 = emqx_broker:topics(), + ?assertEqual(true, lists:foldl(fun(Topic, Acc) -> + case lists:member(Topic, Topics1) of + true -> Acc; + false -> false + end + end, true, Topics)), + emqx_broker:unsubscribe(lists:nth(1, Topics)), + emqx_broker:unsubscribe(lists:nth(2, Topics)), + emqx_broker:unsubscribe(lists:nth(3, Topics)). + +t_subscribers(_) -> + emqx_broker:subscribe(<<"topic">>, <<"clientid">>), + ?assertEqual([self()], emqx_broker:subscribers(<<"topic">>)), + emqx_broker:unsubscribe(<<"topic">>). + +t_subscriptions(_) -> + emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{qos => 1}), + ?assertEqual(#{qos => 1, subid => <<"clientid">>}, + proplists:get_value(<<"topic">>, emqx_broker:subscriptions(self()))), + ?assertEqual(#{qos => 1, subid => <<"clientid">>}, + proplists:get_value(<<"topic">>, emqx_broker:subscriptions(<<"clientid">>))), + emqx_broker:unsubscribe(<<"topic">>). + +t_sub_pub(_) -> + ok = emqx_broker:subscribe(<<"topic">>), + ct:sleep(10), + emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)), ?assert( - receive {deliver, <<"a/b/c">>, _ } -> + receive + {deliver, <<"topic">>, #message{payload = <<"hello">>}} -> true; - P -> - ct:log("Receive Message: ~p~n",[P]) + _ -> + false + after 100 -> + false + end). + +t_nosub_pub(_) -> + ?assertEqual(0, emqx_metrics:val('messages.dropped')), + emqx_broker:publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)), + ?assertEqual(1, emqx_metrics:val('messages.dropped')). + +t_shared_subscribe(_) -> + emqx_broker:subscribe(<<"topic">>, <<"clientid">>, #{share => <<"group">>}), + ct:sleep(10), + emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)), + ?assert(receive + {deliver, <<"topic">>, #message{payload = <<"hello">>}} -> + true; + Msg -> + ct:pal("Msg: ~p", [Msg]), + false + after 100 -> + false + end), + emqx_broker:unsubscribe(<<"$share/group/topic">>). + +t_shared_subscribe_2(_) -> + {ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]), + {ok, _} = emqtt:connect(ConnPid), + {ok, _, [0]} = emqtt:subscribe(ConnPid, <<"$share/group/topic">>, 0), + + {ok, ConnPid2} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid2">>}]), + {ok, _} = emqtt:connect(ConnPid2), + {ok, _, [0]} = emqtt:subscribe(ConnPid2, <<"$share/group2/topic">>, 0), + + ct:sleep(10), + ok = emqtt:publish(ConnPid, <<"topic">>, <<"hello">>, 0), + Msgs = recv_msgs(2), + ?assertEqual(2, length(Msgs)), + ?assertEqual(true, lists:foldl(fun(#{payload := <<"hello">>, topic := <<"topic">>}, Acc) -> + Acc; + (_, _) -> + false + end, true, Msgs)), + emqtt:disconnect(ConnPid), + emqtt:disconnect(ConnPid2). + +t_shared_subscribe_3(_) -> + {ok, ConnPid} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid">>}]), + {ok, _} = emqtt:connect(ConnPid), + {ok, _, [0]} = emqtt:subscribe(ConnPid, <<"$share/group/topic">>, 0), + + {ok, ConnPid2} = emqtt:start_link([{clean_start, true}, {clientid, <<"clientid2">>}]), + {ok, _} = emqtt:connect(ConnPid2), + {ok, _, [0]} = emqtt:subscribe(ConnPid2, <<"$share/group/topic">>, 0), + + ct:sleep(10), + ok = emqtt:publish(ConnPid, <<"topic">>, <<"hello">>, 0), + Msgs = recv_msgs(2), + ?assertEqual(1, length(Msgs)), + emqtt:disconnect(ConnPid), + emqtt:disconnect(ConnPid2). + +t_shard(_) -> + ok = meck:new(emqx_broker_helper, [passthrough, no_history]), + ok = meck:expect(emqx_broker_helper, get_sub_shard, fun(_, _) -> 1 end), + emqx_broker:subscribe(<<"topic">>, <<"clientid">>), + ct:sleep(10), + emqx_broker:safe_publish(emqx_message:make(ct, <<"topic">>, <<"hello">>)), + ?assert( + receive + {deliver, <<"topic">>, #message{payload = <<"hello">>}} -> + true; + _ -> + false after 100 -> false end), - spawn(fun() -> - emqx_broker:subscribe(<<"a/b/c">>), - emqx_broker:subscribe(<<"c/d/e">>), - timer:sleep(10), - emqx_broker:unsubscribe(<<"a/b/c">>) - end), - timer:sleep(20), - emqx_broker:unsubscribe(<<"a/b/c">>). - -t_shared_subscribe(_) -> - emqx_broker:subscribe(<<"$share/group2/topic2">>), - emqx_broker:subscribe(<<"$queue/topic3">>), - timer:sleep(10), - 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()))). - -'t_pubsub#'(_) -> - emqx_broker:subscribe(<<"a/#">>), - timer:sleep(10), - emqx_broker:publish(emqx_message:make(ct, <<"a/b/c">>, <<"hello">>)), - ?assert(receive {deliver, <<"a/#">>, _} -> true after 100 -> false end), - emqx_broker:unsubscribe(<<"a/#">>). - -'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). - -%%-------------------------------------------------------------------- -%% Stats Group -%%-------------------------------------------------------------------- - -set_get_stat(_) -> - emqx_stats:setstat('retained.max', 99), - ?assertEqual(99, emqx_stats:getstat('retained.max')). - - -t_dispatch(_) -> - error('TODO'). - -t_subscriber_down(_) -> - error('TODO'). - -t_get_subopts(_) -> - error('TODO'). - -t_set_subopts(_) -> - error('TODO'). - -t_topics(_) -> - error('TODO'). + ok = meck:unload(emqx_broker_helper). t_stats_fun(_) -> - error('TODO'). + ?assertEqual(0, emqx_stats:getstat('subscribers.count')), + ?assertEqual(0, emqx_stats:getstat('subscriptions.count')), + ?assertEqual(0, emqx_stats:getstat('suboptions.count')), + ok = emqx_broker:subscribe(<<"topic">>, <<"clientid">>), + ok = emqx_broker:subscribe(<<"topic2">>, <<"clientid">>), + emqx_broker:stats_fun(), + ct:sleep(10), + ?assertEqual(2, emqx_stats:getstat('subscribers.count')), + ?assertEqual(2, emqx_stats:getstat('subscribers.max')), + ?assertEqual(2, emqx_stats:getstat('subscriptions.count')), + ?assertEqual(2, emqx_stats:getstat('subscriptions.max')), + ?assertEqual(2, emqx_stats:getstat('suboptions.count')), + ?assertEqual(2, emqx_stats:getstat('suboptions.max')). -t_init(_) -> - error('TODO'). +recv_msgs(Count) -> + recv_msgs(Count, []). -t_handle_call(_) -> - error('TODO'). - -t_handle_cast(_) -> - error('TODO'). - -t_handle_info(_) -> - error('TODO'). - -t_terminate(_) -> - error('TODO'). - -t_code_change(_) -> - error('TODO'). - -t_safe_publish(_) -> - error('TODO'). - -t_subscribed(_) -> - error('TODO'). - -t_subscriptions(_) -> - error('TODO'). - -t_subscribers(_) -> - error('TODO'). - -t_unsubscribe(_) -> - error('TODO'). - -t_subscribe(_) -> - error('TODO'). +recv_msgs(0, Msgs) -> + Msgs; +recv_msgs(Count, Msgs) -> + receive + {publish, Msg} -> + recv_msgs(Count-1, [Msg|Msgs]); + _Other -> recv_msgs(Count, Msgs) + after 100 -> + Msgs + end. \ No newline at end of file diff --git a/test/emqx_broker_helper_SUITE.erl b/test/emqx_broker_helper_SUITE.erl index e027bb1ef..4e287f341 100644 --- a/test/emqx_broker_helper_SUITE.erl +++ b/test/emqx_broker_helper_SUITE.erl @@ -23,48 +23,52 @@ all() -> emqx_ct:all(?MODULE). +init_per_suite(Config) -> + emqx_ct_helpers:boot_modules(all), + emqx_ct_helpers:start_apps([]), + Config. + +end_per_suite(_Config) -> + emqx_ct_helpers:stop_apps([]). + init_per_testcase(_TestCase, Config) -> Config. end_per_testcase(_TestCase, Config) -> Config. -t_start_link(_) -> - error('TODO'). - t_lookup_subid(_) -> - error('TODO'). - -t_create_seq(_) -> - error('TODO'). - -t_init(_) -> - error('TODO'). - -t_handle_call(_) -> - error('TODO'). - -t_handle_cast(_) -> - error('TODO'). - -t_handle_info(_) -> - error('TODO'). - -t_terminate(_) -> - error('TODO'). - -t_code_change(_) -> - error('TODO'). + ?assertEqual(undefined, emqx_broker_helper:lookup_subid(self())), + emqx_broker_helper:register_sub(self(), <<"clientid">>), + ct:sleep(10), + ?assertEqual(<<"clientid">>, emqx_broker_helper:lookup_subid(self())). t_lookup_subpid(_) -> - error('TODO'). - -t_reclaim_seq(_) -> - error('TODO'). - -t_get_sub_shard(_) -> - error('TODO'). - + ?assertEqual(undefined, emqx_broker_helper:lookup_subpid(<<"clientid">>)), + emqx_broker_helper:register_sub(self(), <<"clientid">>), + ct:sleep(10), + ?assertEqual(self(), emqx_broker_helper:lookup_subpid(<<"clientid">>)). + t_register_sub(_) -> - error('TODO'). + ok = emqx_broker_helper:register_sub(self(), <<"clientid">>), + ct:sleep(10), + ok = emqx_broker_helper:register_sub(self(), <<"clientid">>), + try emqx_broker_helper:register_sub(self(), <<"clientid2">>) of + _ -> ct:fail(should_throw_error) + catch error:Reason -> + ?assertEqual(Reason, subid_conflict) + end, + ?assertEqual(self(), emqx_broker_helper:lookup_subpid(<<"clientid">>)). +t_shard_seq(_) -> + ?assertEqual([], ets:lookup(emqx_subseq, <<"topic">>)), + emqx_broker_helper:create_seq(<<"topic">>), + ?assertEqual([{<<"topic">>, 1}], ets:lookup(emqx_subseq, <<"topic">>)), + emqx_broker_helper:reclaim_seq(<<"topic">>), + ?assertEqual([], ets:lookup(emqx_subseq, <<"topic">>)). + +t_shards_num(_) -> + ?assertEqual(emqx_vm:schedulers() * 32, emqx_broker_helper:shards_num()). + +t_get_sub_shard(_) -> + ?assertEqual(0, emqx_broker_helper:get_sub_shard(self(), <<"topic">>)). diff --git a/test/emqx_cm_registry_SUITE.erl b/test/emqx_cm_registry_SUITE.erl index ac0988c7f..56f539eba 100644 --- a/test/emqx_cm_registry_SUITE.erl +++ b/test/emqx_cm_registry_SUITE.erl @@ -21,44 +21,57 @@ -include_lib("eunit/include/eunit.hrl"). +%%-------------------------------------------------------------------- +%% CT callbacks +%%-------------------------------------------------------------------- + all() -> emqx_ct:all(?MODULE). +init_per_suite(Config) -> + emqx_ct_helpers:boot_modules(all), + emqx_ct_helpers:start_apps([]), + Config. + +end_per_suite(_Config) -> + emqx_ct_helpers:stop_apps([]). + init_per_testcase(_TestCase, Config) -> Config. end_per_testcase(_TestCase, Config) -> Config. -t_start_link(_) -> - error('TODO'). - -t_init(_) -> - error('TODO'). - -t_handle_call(_) -> - error('TODO'). - -t_handle_cast(_) -> - error('TODO'). - -t_handle_info(_) -> - error('TODO'). - -t_terminate(_) -> - error('TODO'). - -t_code_change(_) -> - error('TODO'). - -t_lookup_channels(_) -> - error('TODO'). - t_is_enabled(_) -> - error('TODO'). + application:set_env(emqx, enable_channel_registry, false), + ?assertEqual(false, emqx_cm_registry:is_enabled()), + application:set_env(emqx, enable_channel_registry, true), + ?assertEqual(true, emqx_cm_registry:is_enabled()). -t_unregister_channel(_) -> - error('TODO'). +t_register_unregister_channel(_) -> + ClientId = <<"clientid">>, + application:set_env(emqx, enable_channel_registry, false), + emqx_cm_registry:register_channel(ClientId), + ?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)), -t_register_channel(_) -> - error('TODO'). + application:set_env(emqx, enable_channel_registry, true), + emqx_cm_registry:register_channel(ClientId), + ?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)), + application:set_env(emqx, enable_channel_registry, false), + emqx_cm_registry:unregister_channel(ClientId), + ?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)), + + application:set_env(emqx, enable_channel_registry, true), + emqx_cm_registry:unregister_channel(ClientId), + ?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)). + +t_cleanup_channels(_) -> + ClientId = <<"clientid">>, + ClientId2 = <<"clientid2">>, + emqx_cm_registry:register_channel(ClientId), + emqx_cm_registry:register_channel(ClientId2), + ?assertEqual([self()], emqx_cm_registry:lookup_channels(ClientId)), + emqx_cm_registry ! {membership, {mnesia, down, node()}}, + ct:sleep(100), + ?assertEqual([], emqx_cm_registry:lookup_channels(ClientId)), + ?assertEqual([], emqx_cm_registry:lookup_channels(ClientId2)). diff --git a/test/emqx_metrics_SUITE.erl b/test/emqx_metrics_SUITE.erl index 2b704d085..a9445958b 100644 --- a/test/emqx_metrics_SUITE.erl +++ b/test/emqx_metrics_SUITE.erl @@ -24,24 +24,10 @@ all() -> emqx_ct:all(?MODULE). -t_val(_) -> - error('TODO'). - -t_dec(_) -> - error('TODO'). - -t_set(_) -> - error('TODO'). - -t_commit(_) -> - error('TODO'). - -t_inc(_) -> - error('TODO'). - t_new(_) -> with_metrics_server( fun() -> + ok = emqx_metrics:new('metrics.test'), ok = emqx_metrics:new('metrics.test'), 0 = emqx_metrics:val('metrics.test'), ok = emqx_metrics:inc('metrics.test'), @@ -86,16 +72,70 @@ t_inc_recv(_) -> 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')) + ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(0, 0)), + ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(1, 0)), + ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(2, 0)), + ok = emqx_metrics:inc_recv(?PUBLISH_PACKET(3, 0)), + ok = emqx_metrics:inc_recv(?PACKET(?PUBACK)), + ok = emqx_metrics:inc_recv(?PACKET(?PUBREC)), + ok = emqx_metrics:inc_recv(?PACKET(?PUBREL)), + ok = emqx_metrics:inc_recv(?PACKET(?PUBCOMP)), + ok = emqx_metrics:inc_recv(?PACKET(?SUBSCRIBE)), + ok = emqx_metrics:inc_recv(?PACKET(?UNSUBSCRIBE)), + ok = emqx_metrics:inc_recv(?PACKET(?PINGREQ)), + ok = emqx_metrics:inc_recv(?PACKET(?DISCONNECT)), + ok = emqx_metrics:inc_recv(?PACKET(?AUTH)), + ignore = emqx_metrics:inc_recv(?PACKET(?RESERVED)), + ?assertEqual(15, emqx_metrics:val('packets.received')), + ?assertEqual(1, emqx_metrics:val('packets.connect.received')), + ?assertEqual(4, emqx_metrics:val('messages.received')), + ?assertEqual(1, emqx_metrics:val('messages.qos0.received')), + ?assertEqual(1, emqx_metrics:val('messages.qos1.received')), + ?assertEqual(1, emqx_metrics:val('messages.qos2.received')), + ?assertEqual(4, emqx_metrics:val('packets.publish.received')), + ?assertEqual(1, emqx_metrics:val('packets.puback.received')), + ?assertEqual(1, emqx_metrics:val('packets.pubrec.received')), + ?assertEqual(1, emqx_metrics:val('packets.pubrel.received')), + ?assertEqual(1, emqx_metrics:val('packets.pubcomp.received')), + ?assertEqual(1, emqx_metrics:val('packets.subscribe.received')), + ?assertEqual(1, emqx_metrics:val('packets.unsubscribe.received')), + ?assertEqual(1, emqx_metrics:val('packets.pingreq.received')), + ?assertEqual(1, emqx_metrics:val('packets.disconnect.received')), + ?assertEqual(1, emqx_metrics:val('packets.auth.received')) end). t_inc_sent(_) -> 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')) + ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(0, 0)), + ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(1, 0)), + ok = emqx_metrics:inc_sent(?PUBLISH_PACKET(2, 0)), + ok = emqx_metrics:inc_sent(?PUBACK_PACKET(0, 0)), + ok = emqx_metrics:inc_sent(?PUBREC_PACKET(3, 0)), + ok = emqx_metrics:inc_sent(?PACKET(?PUBREL)), + ok = emqx_metrics:inc_sent(?PACKET(?PUBCOMP)), + ok = emqx_metrics:inc_sent(?PACKET(?SUBACK)), + ok = emqx_metrics:inc_sent(?PACKET(?UNSUBACK)), + ok = emqx_metrics:inc_sent(?PACKET(?PINGRESP)), + ok = emqx_metrics:inc_sent(?PACKET(?DISCONNECT)), + ok = emqx_metrics:inc_sent(?PACKET(?AUTH)), + ?assertEqual(13, emqx_metrics:val('packets.sent')), + ?assertEqual(1, emqx_metrics:val('packets.connack.sent')), + ?assertEqual(3, emqx_metrics:val('messages.sent')), + ?assertEqual(1, emqx_metrics:val('messages.qos0.sent')), + ?assertEqual(1, emqx_metrics:val('messages.qos1.sent')), + ?assertEqual(1, emqx_metrics:val('messages.qos2.sent')), + ?assertEqual(3, emqx_metrics:val('packets.publish.sent')), + ?assertEqual(1, emqx_metrics:val('packets.puback.sent')), + ?assertEqual(1, emqx_metrics:val('packets.pubrec.sent')), + ?assertEqual(1, emqx_metrics:val('packets.pubrel.sent')), + ?assertEqual(1, emqx_metrics:val('packets.pubcomp.sent')), + ?assertEqual(1, emqx_metrics:val('packets.suback.sent')), + ?assertEqual(1, emqx_metrics:val('packets.unsuback.sent')), + ?assertEqual(1, emqx_metrics:val('packets.pingresp.sent')), + ?assertEqual(1, emqx_metrics:val('packets.disconnect.sent')), + ?assertEqual(1, emqx_metrics:val('packets.auth.sent')) end). t_trans(_) -> diff --git a/test/emqx_misc_SUITE.erl b/test/emqx_misc_SUITE.erl index 677b25c17..98bb7b072 100644 --- a/test/emqx_misc_SUITE.erl +++ b/test/emqx_misc_SUITE.erl @@ -64,7 +64,10 @@ t_pipeline(_) -> fun(_I, St) -> {ok, St+1} end, fun(I, St) -> {ok, I+1, St+1} end, fun(I, St) -> {ok, I*2, St*2} end], - ?assertEqual({ok, 4, 6}, emqx_misc:pipeline(Funs, 1, 1)). + ?assertEqual({ok, 4, 6}, emqx_misc:pipeline(Funs, 1, 1)), + ?assertEqual({error, undefined, 1}, emqx_misc:pipeline([fun(_I) -> {error, undefined} end], 1, 1)), + ?assertEqual({error, undefined, 2}, emqx_misc:pipeline([fun(_I, _St) -> {error, undefined, 2} end], 1, 1)), + ?assertEqual({error, undefined, 1}, emqx_misc:pipeline([fun(_I, _St) -> erlang:error(undefined) end], 1, 1)). t_start_timer(_) -> TRef = emqx_misc:start_timer(1, tmsg), @@ -75,7 +78,8 @@ t_start_timer(_) -> t_cancel_timer(_) -> Timer = emqx_misc:start_timer(0, foo), ok = emqx_misc:cancel_timer(Timer), - ?assertEqual([], drain()). + ?assertEqual([], drain()), + ok = emqx_misc:cancel_timer(undefined). t_proc_name(_) -> ?assertEqual(emqx_pool_1, emqx_misc:proc_name(emqx_pool, 1)). @@ -84,7 +88,11 @@ t_proc_stats(_) -> Pid1 = spawn(fun() -> exit(normal) end), timer:sleep(10), ?assertEqual([], emqx_misc:proc_stats(Pid1)), - Pid2 = spawn(fun() -> timer:sleep(100) end), + Pid2 = spawn(fun() -> + ?assertMatch([{mailbox_len, 0}|_], emqx_misc:proc_stats()), + timer:sleep(200) + end), + timer:sleep(10), Pid2 ! msg, timer:sleep(10), ?assertMatch([{mailbox_len, 1}|_], emqx_misc:proc_stats(Pid2)). @@ -100,7 +108,16 @@ t_drain_down(_) -> {Pid1, _Ref1} = erlang:spawn_monitor(fun() -> ok end), {Pid2, _Ref2} = erlang:spawn_monitor(fun() -> ok end), timer:sleep(100), - ?assertEqual([Pid1, Pid2], emqx_misc:drain_down(2)). + ?assertEqual([Pid1, Pid2], emqx_misc:drain_down(2)), + ?assertEqual([], emqx_misc:drain_down(1)). + +t_index_of(_) -> + try emqx_misc:index_of(a, []) of + _ -> ct:fail(should_throw_error) + catch error:Reason -> + ?assertEqual(badarg, Reason) + end, + ?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])). drain() -> drain([]). diff --git a/test/emqx_pqueue_SUITE.erl b/test/emqx_pqueue_SUITE.erl index 377172b18..226cf5423 100644 --- a/test/emqx_pqueue_SUITE.erl +++ b/test/emqx_pqueue_SUITE.erl @@ -26,142 +26,152 @@ all() -> emqx_ct:all(?SUITE). - t_is_queue(_) -> - error('TODO'). + Q = ?PQ:new(), + ?assertEqual(true, ?PQ:is_queue(Q)), + Q1 = ?PQ:in(a, 1, Q), + ?assertEqual(true, ?PQ:is_queue(Q1)), + ?assertEqual(false, ?PQ:is_queue(bad_queue)). t_is_empty(_) -> - error('TODO'). - -t_to_list(_) -> - error('TODO'). - -t_from_list(_) -> - error('TODO'). - -t_in(_) -> - error('TODO'). - -t_out_p(_) -> - error('TODO'). - -t_join(_) -> - error('TODO'). - -t_filter(_) -> - error('TODO'). - -t_fold(_) -> - error('TODO'). - -t_highest(_) -> - error('TODO'). - -t_out(_) -> - error('TODO'). + Q = ?PQ:new(), + ?assertEqual(true, ?PQ:is_empty(Q)), + ?assertEqual(false, ?PQ:is_empty(?PQ:in(a, Q))). t_len(_) -> - error('TODO'). + Q = ?PQ:new(), + Q1 = ?PQ:in(a, Q), + ?assertEqual(1, ?PQ:len(Q1)), + Q2 = ?PQ:in(b, 1, Q1), + ?assertEqual(2, ?PQ:len(Q2)). t_plen(_) -> - error('TODO'). - -t_new(_) -> - error('TODO'). - -t_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). + Q1 = ?PQ:in(a, Q), + ?assertEqual(1, ?PQ:plen(0, Q1)), + ?assertEqual(0, ?PQ:plen(1, Q1)), + Q2 = ?PQ:in(b, 1, Q1), + Q3 = ?PQ:in(c, 1, Q2), + ?assertEqual(2, ?PQ:plen(1, Q3)), + ?assertEqual(1, ?PQ:plen(0, Q3)), + ?assertEqual(0, ?PQ:plen(0, {pqueue, []})). -t_priority_queue_out2(_) -> - Els = [a, {b, 1}, {c, 1}, {d, 2}, {e, 2}, {f, 2}], - Q = ?PQ:new(), - Q0 = lists:foldl( +t_to_list(_) -> + Q = ?PQ:new(), + ?assertEqual([], ?PQ:to_list(Q)), + + Q1 = ?PQ:in(a, Q), + L1 = ?PQ:to_list(Q1), + ?assertEqual([{0, a}], L1), + + Q2 = ?PQ:in(b, 1, Q1), + L2 = ?PQ:to_list(Q2), + ?assertEqual([{1, b}, {0, a}], L2). + +t_from_list(_) -> + Q = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]), + ?assertEqual({pqueue, [{-1, {queue, [d], [c], 2}}, {0, {queue, [b], [a], 2}}]}, Q), + ?assertEqual(true, ?PQ:is_queue(Q)), + ?assertEqual(4, ?PQ:len(Q)). + +t_in(_) -> + Q = ?PQ:new(), + Els = [a, b, {c, 1}, {d, 1}, {e, infinity}, {f, 2}], + Q1 = 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). + ?assertEqual({pqueue, [{infinity, {queue, [e], [], 1}}, + {-2, {queue, [f], [], 1}}, + {-1, {queue, [d], [c], 2}}, + {0, {queue, [b], [a], 2}}]}, Q1). -t_priority_queues(_) -> - Q0 = ?PQ:new(), - Q1 = ?PQ:new(), - PQueue = {pqueue, [{0, Q0}, {1, Q1}]}, - ?assert(?PQ:is_queue(PQueue)), - [] = ?PQ:to_list(PQueue), +t_out(_) -> + Q = ?PQ:new(), + {empty, Q} = ?PQ:out(Q), + {empty, Q} = ?PQ:out(0, Q), + try ?PQ:out(1, Q) of + _ -> ct:fail(should_throw_error) + catch error:Reason -> + ?assertEqual(Reason, badarg) + end, + {{value, a}, Q} = ?PQ:out(?PQ:from_list([{0, a}])), + {{value, a}, {queue, [], [b], 1}} = ?PQ:out(?PQ:from_list([{0, a}, {0, b}])), + {{value, a}, {queue, [], [], 0}} = ?PQ:out({queue, [], [a], 1}), + {{value, a}, {queue, [c], [b], 2}} = ?PQ:out({queue, [c, b], [a], 3}), + {{value, a}, {queue, [e, d], [b, c], 4}} = ?PQ:out({queue, [e, d, c, b], [a], 5}), + {{value, a}, {queue, [c], [b], 2}} = ?PQ:out({queue, [c, b, a], [], 3}), + {{value, a}, {queue, [d, c], [b], 3}} = ?PQ:out({queue, [d, c], [a, b], 4}), + {{value, a}, {queue, [], [], 0}} = ?PQ:out(?PQ:from_list([{1, a}])), + {{value, a}, {queue, [c], [b], 2}} = ?PQ:out(?PQ:from_list([{1, a}, {0, b}, {0, c}])), + {{value, a}, {pqueue, [{-1, {queue, [b], [], 1}}]}} = ?PQ:out(?PQ:from_list([{1, b}, {2, a}])), + {{value, a}, {pqueue, [{-1, {queue, [], [b], 1}}]}} = ?PQ:out(?PQ:from_list([{1, a}, {1, b}])). - PQueue1 = ?PQ:in(a, 0, ?PQ:new()), - PQueue2 = ?PQ:in(b, 0, PQueue1), +t_out_2(_) -> + {empty, {pqueue, [{-1, {queue, [a], [], 1}}]}} = ?PQ:out(0, ?PQ:from_list([{1, a}])), + {{value, a}, {queue, [], [], 0}} = ?PQ:out(1, ?PQ:from_list([{1, a}])), + {{value, a}, {pqueue, [{-1, {queue, [], [b], 1}}]}} = ?PQ:out(1, ?PQ:from_list([{1, a}, {1, b}])), + {{value, a}, {queue, [b], [], 1}} = ?PQ:out(1, ?PQ:from_list([{1, a}, {0, b}])). - PQueue3 = ?PQ:in(c, 1, PQueue2), - PQueue4 = ?PQ:in(d, 1, PQueue3), +t_out_p(_) -> + {empty, {queue, [], [], 0}} = ?PQ:out_p(?PQ:new()), + {{value, a, 1}, {queue, [b], [], 1}} = ?PQ:out_p(?PQ:from_list([{1, a}, {0, b}])). - 4 = ?PQ:len(PQueue4), +t_join(_) -> + Q = ?PQ:in(a, ?PQ:new()), + Q = ?PQ:join(Q, ?PQ:new()), + Q = ?PQ:join(?PQ:new(), Q), - [{1, c}, {1, d}, {0, a}, {0, b}] = ?PQ:to_list(PQueue4), - PQueue4 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]), + Q1 = ?PQ:in(a, ?PQ:new()), + Q2 = ?PQ:in(b, Q1), + Q3 = ?PQ:in(c, Q2), + {queue,[c,b],[a],3} = Q3, + Q4 = ?PQ:in(x, ?PQ:new()), + Q5 = ?PQ:in(y, Q4), + Q6 = ?PQ:in(z, Q5), + {queue,[z,y],[x],3} = Q6, + + {queue,[z,y],[a,b,c,x],6} = ?PQ:join(Q3, Q6), + + PQueue1 = ?PQ:from_list([{1, c}, {1, d}]), + PQueue2 = ?PQ:from_list([{1, c}, {1, d}, {0, a}, {0, b}]), + PQueue3 = ?PQ:from_list([{1, c}, {1, d}, {-1, a}, {-1, b}]), + + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[z,y],[x],3}}]} = ?PQ:join(PQueue1, Q6), + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[z,y],[x],3}}]} = ?PQ:join(Q6, PQueue1), + + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[z,y],[a,b,x],5}}]} = ?PQ:join(PQueue2, Q6), + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[b],[x,y,z,a],5}}]} = ?PQ:join(Q6, PQueue2), + + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[z,y],[x],3}}, + {1,{queue,[b],[a],2}}]} = ?PQ:join(PQueue3, Q6), + {pqueue,[{-1,{queue,[d],[c],2}}, + {0,{queue,[z,y],[x],3}}, + {1,{queue,[b],[a],2}}]} = ?PQ:join(Q6, PQueue3), + + PQueue4 = ?PQ:from_list([{1, c}, {1, d}]), + PQueue5 = ?PQ:from_list([{2, a}, {2, b}]), + {pqueue,[{-2,{queue,[b],[a],2}}, + {-1,{queue,[d],[c],2}}]} = ?PQ:join(PQueue4, PQueue5). + +t_filter(_) -> + {pqueue, [{-2, {queue, [10], [4], 2}}, + {-1, {queue, [2], [], 1}}]} = + ?PQ:filter(fun(V) when V rem 2 =:= 0 -> + true; + (_) -> + false + end, ?PQ:from_list([{0, 1}, {0, 3}, {1, 2}, {2, 4}, {2, 10}])). + +t_highest(_) -> empty = ?PQ:highest(?PQ:new()), - 0 = ?PQ:highest(PQueue1), - 1 = ?PQ:highest(PQueue4), - - PQueue5 = ?PQ:in(e, infinity, PQueue4), - PQueue6 = ?PQ:in(f, 1, PQueue5), - - {{value, e}, PQueue7} = ?PQ:out(PQueue6), - {empty, _} = ?PQ:out(0, ?PQ:new()), - - {empty, Q0} = ?PQ:out_p(Q0), - - Q2 = ?PQ:in(a, Q0), - Q3 = ?PQ:in(b, Q2), - Q4 = ?PQ:in(c, Q3), - - {{value, a, 0}, _Q5} = ?PQ:out_p(Q4), - - {{value,c,1}, PQueue8} = ?PQ:out_p(PQueue7), - - Q4 = ?PQ:join(Q4, ?PQ:new()), - Q4 = ?PQ:join(?PQ:new(), Q4), - - {queue, [a], [a], 2} = ?PQ:join(Q2, Q2), - - {pqueue,[{-1,{queue,[f],[d],2}}, - {0,{queue,[a],[a,b],3}}]} = ?PQ:join(PQueue8, Q2), - - {pqueue,[{-1,{queue,[f],[d],2}}, - {0,{queue,[b],[a,a],3}}]} = ?PQ:join(Q2, PQueue8), - - {pqueue,[{-1,{queue,[f],[d,f,d],4}}, - {0,{queue,[b],[a,b,a],4}}]} = ?PQ:join(PQueue8, PQueue8). - + 0 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}])), + 2 = ?PQ:highest(?PQ:from_list([{0, a}, {0, b}, {1, c}, {2, d}, {2, e}])). \ No newline at end of file diff --git a/test/emqx_vm_SUITE.erl b/test/emqx_vm_SUITE.erl index c4532ed26..0e38c91c4 100644 --- a/test/emqx_vm_SUITE.erl +++ b/test/emqx_vm_SUITE.erl @@ -104,7 +104,8 @@ t_load(_Config) -> t_systeminfo(_Config) -> Keys = [Key || {Key, _} <- emqx_vm:get_system_info()], - ?SYSTEM_INFO = Keys. + ?SYSTEM_INFO = Keys, + ?assertEqual(undefined, emqx_vm:get_system_info(undefined)). t_mem_info(_Config) -> application:ensure_all_started(os_mon), @@ -139,10 +140,19 @@ 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). + test = proplists:get_value(name, EtsInfo), + Tid = proplists:get_value(id, EtsInfo), + EtsInfos = emqx_vm:get_ets_info(), + ?assertEqual(true, lists:foldl(fun(Info, Acc) -> + case proplists:get_value(id, Info) of + Tid -> true; + _ -> Acc + end + end, false, EtsInfos)). t_get_ets_object(_Config) -> ets:new(test, [named_table]), + [] = emqx_vm:get_ets_object(test), ets:insert(test, {k, v}), [{k, v}] = emqx_vm:get_ets_object(test). @@ -150,7 +160,20 @@ t_get_port_types(_Config) -> emqx_vm:get_port_types(). t_get_port_info(_Config) -> - emqx_vm:get_port_info(). + emqx_vm:get_port_info(), + spawn(fun easy_server/0), + ct:sleep(100), + {ok, Sock} = gen_tcp:connect("localhost", 5678, [binary, {packet, 0}]), + emqx_vm:get_port_info(), + ok = gen_tcp:close(Sock), + [Port | _] = erlang:ports(), + [{connected, _}, {name, _}] = emqx_vm:port_info(Port, [connected, name]). + +t_transform_port(_Config) -> + [Port | _] = erlang:ports(), + ?assertEqual(Port, emqx_vm:transform_port(Port)), + <<131, 102, 100, NameLen:2/unit:8, _Name:NameLen/binary, N:4/unit:8, _Vsn:8>> = erlang:term_to_binary(Port), + ?assertEqual(Port, emqx_vm:transform_port("#Port<0." ++ integer_to_list(N) ++ ">")). t_scheduler_usage(_Config) -> emqx_vm:scheduler_usage(5000). @@ -170,3 +193,21 @@ t_get_process_group_leader_info(_Config) -> t_get_process_limit(_Config) -> emqx_vm:get_process_limit(). +t_cpu_util(_Config) -> + ?assertEqual(0, emqx_vm:cpu_util()). + +easy_server() -> + {ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}, {active, false}]), + {ok, Sock} = gen_tcp:accept(LSock), + ok = do_recv(Sock), + ok = gen_tcp:close(Sock), + ok = gen_tcp:close(LSock). + +do_recv(Sock) -> + case gen_tcp:recv(Sock, 0) of + {ok, _} -> + do_recv(Sock); + {error, closed} -> + ok + end. + From 0e81924034d7de0d84c85fc4fbb12f95d1f63b4e Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Fri, 1 Nov 2019 16:37:20 +0800 Subject: [PATCH 30/40] Add more test cases for emqx --- test/emqx_SUITE.erl | 192 +++++++++++++++++++++++++++----------------- 1 file changed, 117 insertions(+), 75 deletions(-) diff --git a/test/emqx_SUITE.erl b/test/emqx_SUITE.erl index 7fcb51332..1ea78a446 100644 --- a/test/emqx_SUITE.erl +++ b/test/emqx_SUITE.erl @@ -25,7 +25,6 @@ all() -> emqx_ct:all(?MODULE). - init_per_suite(Config) -> emqx_ct_helpers:start_apps([]), Config. @@ -33,60 +32,20 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). - -t_start(_) -> - error('TODO'). - -t_restart(_) -> - error('TODO'). - -t_stop(_) -> - error('TODO'). - -t_is_running(_) -> - error('TODO'). - -t_subscribe(_) -> - error('TODO'). - -t_publish(_) -> - error('TODO'). - -t_unsubscribe(_) -> - error('TODO'). - -t_topics(_) -> - error('TODO'). - -t_subscribers(_) -> - error('TODO'). - -t_subscriptions(_) -> - error('TODO'). - -t_subscribed(_) -> - error('TODO'). - -t_hook(_) -> - error('TODO'). - -t_unhook(_) -> - error('TODO'). - -t_run_hook(_) -> - error('TODO'). - -t_run_fold_hook(_) -> - error('TODO'). - -t_shutdown(_) -> - error('TODO'). - -t_reboot(_) -> - error('TODO'). - +t_stop_start(_) -> + emqx:stop(), + false = emqx:is_running(node()), + emqx:start(), + true = emqx:is_running(node()), + ok = emqx:shutdown(), + false = emqx:is_running(node()), + ok = emqx:reboot(), + true = emqx:is_running(node()), + ok = emqx:shutdown(for_test), + false = emqx:is_running(node()). t_get_env(_) -> + emqx:start(), ?assertEqual(undefined, emqx:get_env(undefined_key)), ?assertEqual(default_value, emqx:get_env(undefined_key, default_value)), application:set_env(emqx, undefined_key, hello), @@ -95,23 +54,32 @@ t_get_env(_) -> application:unset_env(emqx, undefined_key). t_emqx_pubsub_api(_) -> - emqx:start(), true = emqx:is_running(node()), {ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]), {ok, _} = emqtt:connect(C), ClientId = <<"myclient">>, - Topic = <<"mytopic">>, Payload = <<"Hello World">>, + Topic = <<"mytopic">>, Topic1 = <<"mytopic1">>, + Topic2 = <<"mytopic2">>, + Topic3 = <<"mytopic3">>, emqx:subscribe(Topic, ClientId), + emqx:subscribe(Topic1, ClientId, #{qos => 1}), + emqx:subscribe(Topic2, ClientId, #{qos => 2}), ct:sleep(100), - ?assertEqual([Topic], emqx:topics()), + ?assertEqual([Topic2, Topic1, Topic], emqx:topics()), ?assertEqual([self()], emqx:subscribers(Topic)), - ?assertEqual([{Topic,#{qos => 0,subid => ClientId}}], emqx:subscriptions(self())), + ?assertEqual([self()], emqx:subscribers(Topic1)), + ?assertEqual([self()], emqx:subscribers(Topic2)), + ?assertEqual([{Topic,#{qos => 0,subid => ClientId}}, {Topic1,#{qos => 1,subid => ClientId}}, {Topic2,#{qos => 2,subid => ClientId}}], emqx:subscriptions(self())), ?assertEqual(true, emqx:subscribed(self(), Topic)), ?assertEqual(true, emqx:subscribed(ClientId, Topic)), - ?assertEqual(false, emqx:subscribed(self(), Topic1)), - ?assertEqual(false, emqx:subscribed(ClientId, Topic1)), + ?assertEqual(true, emqx:subscribed(self(), Topic1)), + ?assertEqual(true, emqx:subscribed(ClientId, Topic1)), + ?assertEqual(true, emqx:subscribed(self(), Topic2)), + ?assertEqual(true, emqx:subscribed(ClientId, Topic2)), + ?assertEqual(false, emqx:subscribed(self(), Topic3)), + ?assertEqual(false, emqx:subscribed(ClientId, Topic3)), emqx:publish(emqx_message:make(Topic, Payload)), receive {deliver, Topic, #message{payload = Payload}} -> @@ -119,26 +87,100 @@ t_emqx_pubsub_api(_) -> after 100 -> ct:fail("no_message") end, + emqx:publish(emqx_message:make(Topic1, Payload)), + receive + {deliver, Topic1, #message{payload = Payload}} -> + ok + after 100 -> + ct:fail("no_message") + end, + emqx:publish(emqx_message:make(Topic2, Payload)), + receive + {deliver, Topic2, #message{payload = Payload}} -> + ok + after 100 -> + ct:fail("no_message") + end, emqx:unsubscribe(Topic), + emqx:unsubscribe(Topic1), + emqx:unsubscribe(Topic2), ct:sleep(20), ?assertEqual([], emqx:topics()). -t_emqx_hook_api(_) -> - InitArgs = ['arg2', 'arg3'], - emqx:hook('hook.run', fun run/3, InitArgs), - ok = emqx:run_hook('hook.run', ['arg1']), - emqx:unhook('hook.run', fun run/3), +t_hook_unhook(_) -> + ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []), + ok = emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, []), + ?assertEqual({error, already_exists}, + emqx:hook(test_hook, fun ?MODULE:hook_fun2/1, [])), + ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun1/1), + ok = emqx:unhook(test_hook, fun ?MODULE:hook_fun2/1), - emqx:hook('hook.run_fold', fun add1/1), - emqx:hook('hook.run_fold', fun add2/1), - 4 = emqx:run_fold_hook('hook.run_fold', [], 1), - emqx:unhook('hook.run_fold', fun add1/1), - emqx:unhook('hook.run_fold', fun add2/1). + ok = emqx:hook(emqx_hook, {?MODULE, hook_fun8, []}, 8), + ok = emqx:hook(emqx_hook, {?MODULE, hook_fun2, []}, 2), + ok = emqx:hook(emqx_hook, {?MODULE, hook_fun10, []}, 10), + ok = emqx:hook(emqx_hook, {?MODULE, hook_fun9, []}, 9), + ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun2, []}), + ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun8, []}), + ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun9, []}), + ok = emqx:unhook(emqx_hook, {?MODULE, hook_fun10, []}). -run('arg1', 'arg2', 'arg3') -> - ok; -run(_, _, _) -> - ct:fail("no_match"). +t_run_hook(_) -> + ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun3/4, [init]), + ok = emqx:hook(foldl_hook, {?MODULE, hook_fun3, [init]}), + ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun4/4, [init]), + ok = emqx:hook(foldl_hook, fun ?MODULE:hook_fun5/4, [init]), + [r5,r4] = emqx:run_fold_hook(foldl_hook, [arg1, arg2], []), + [] = emqx:run_fold_hook(unknown_hook, [], []), -add1(N) -> {ok, N + 1}. -add2(N) -> {ok, N + 2}. + ok = emqx:hook(foldl_hook2, fun ?MODULE:hook_fun9/2), + ok = emqx:hook(foldl_hook2, {?MODULE, hook_fun10, []}), + [r9] = emqx:run_fold_hook(foldl_hook2, [arg], []), + + ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]), + {error, already_exists} = emqx:hook(foreach_hook, fun ?MODULE:hook_fun6/2, [initArg]), + ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun7/2, [initArg]), + ok = emqx:hook(foreach_hook, fun ?MODULE:hook_fun8/2, [initArg]), + ok = emqx:run_hook(foreach_hook, [arg]), + + ok = emqx:hook(foreach_filter1_hook, {?MODULE, hook_fun1, []}, {?MODULE, hook_filter1, []}, 0), + ?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg])), %% filter passed + ?assertEqual(ok, emqx:run_hook(foreach_filter1_hook, [arg1])), %% filter failed + + ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2, []}, {?MODULE, hook_filter2, [init_arg]}), + ok = emqx:hook(foldl_filter2_hook, {?MODULE, hook_fun2_1, []}, {?MODULE, hook_filter2_1, [init_arg]}), + ?assertEqual(3, emqx:run_fold_hook(foldl_filter2_hook, [arg], 1)), + ?assertEqual(2, emqx:run_fold_hook(foldl_filter2_hook, [arg1], 1)). + +%%-------------------------------------------------------------------- +%% Hook fun +%%-------------------------------------------------------------------- + +hook_fun1(arg) -> ok; +hook_fun1(_) -> error. + +hook_fun2(arg) -> ok; +hook_fun2(_) -> error. + +hook_fun2(_, Acc) -> {ok, Acc + 1}. +hook_fun2_1(_, Acc) -> {ok, Acc + 1}. + +hook_fun3(arg1, arg2, _Acc, init) -> ok. +hook_fun4(arg1, arg2, Acc, init) -> {ok, [r4 | Acc]}. +hook_fun5(arg1, arg2, Acc, init) -> {ok, [r5 | Acc]}. + +hook_fun6(arg, initArg) -> ok. +hook_fun7(arg, initArg) -> ok. +hook_fun8(arg, initArg) -> ok. + +hook_fun9(arg, Acc) -> {stop, [r9 | Acc]}. +hook_fun10(arg, Acc) -> {stop, [r10 | Acc]}. + +hook_filter1(arg) -> true; +hook_filter1(_) -> false. + +hook_filter2(arg, _Acc, init_arg) -> true; +hook_filter2(_, _Acc, _IntArg) -> false. + +hook_filter2_1(arg, _Acc, init_arg) -> true; +hook_filter2_1(arg1, _Acc, init_arg) -> true; +hook_filter2_1(_, _Acc, _IntArg) -> false. \ No newline at end of file From e3ddd4ebe9a61248b3d66c787b6d326d39cd1744 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Fri, 1 Nov 2019 16:58:53 +0800 Subject: [PATCH 31/40] Make test cases pass temporarily --- test/emqx_access_control_SUITE.erl | 12 +++---- test/emqx_access_rule_SUITE.erl | 8 ++--- test/emqx_acl_cache_SUITE.erl | 48 ++++++++++++++-------------- test/emqx_base62_SUITE.erl | 7 ---- test/emqx_batch_SUITE.erl | 16 ---------- test/emqx_cm_SUITE.erl | 32 +++++++++---------- test/emqx_cm_locker_SUITE.erl | 16 +++++----- test/emqx_flapping_SUITE.erl | 8 ----- test/emqx_guid_SUITE.erl | 23 ------------- test/emqx_hooks_SUITE.erl | 24 ++++++-------- test/emqx_inflight_SUITE.erl | 15 ++------- test/emqx_keepalive_SUITE.erl | 7 ---- test/emqx_logger_SUITE.erl | 4 +-- test/emqx_message_SUITE.erl | 21 ++---------- test/emqx_mod_acl_internal_SUITE.erl | 20 ++++++------ test/emqx_mod_presence_SUITE.erl | 16 +++++----- test/emqx_mod_rewrite_SUITE.erl | 20 ++++++------ test/emqx_mod_subscription_SUITE.erl | 12 +++---- test/emqx_modules_SUITE.erl | 9 +++--- test/emqx_mqtt_caps_SUITE.erl | 10 +++--- test/emqx_mqtt_props_SUITE.erl | 12 +++---- test/emqx_mqueue_SUITE.erl | 24 +++++++------- test/emqx_oom_SUITE.erl | 5 ++- test/emqx_os_mon_SUITE.erl | 40 ++++------------------- test/emqx_pd_SUITE.erl | 6 ---- test/emqx_plugins_SUITE.erl | 22 ++++++------- test/emqx_pmon_SUITE.erl | 16 +++++----- test/emqx_pool_SUITE.erl | 5 ++- test/emqx_reason_codes_SUITE.erl | 25 +++++++-------- test/emqx_router_SUITE.erl | 24 +++++++------- test/emqx_router_helper_SUITE.erl | 13 ++++---- test/emqx_sequence_SUITE.erl | 22 ++++++------- test/emqx_shared_sub_SUITE.erl | 33 ++++++++++--------- test/emqx_sys_SUITE.erl | 29 ++++++++--------- 34 files changed, 232 insertions(+), 372 deletions(-) diff --git a/test/emqx_access_control_SUITE.erl b/test/emqx_access_control_SUITE.erl index 06483b895..53bdba35d 100644 --- a/test/emqx_access_control_SUITE.erl +++ b/test/emqx_access_control_SUITE.erl @@ -29,12 +29,12 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_authenticate(_) -> - error('TODO'). +% t_authenticate(_) -> +% error('TODO'). -t_check_acl(_) -> - error('TODO'). +% t_check_acl(_) -> +% error('TODO'). -t_reload_acl(_) -> - error('TODO'). +% t_reload_acl(_) -> +% error('TODO'). diff --git a/test/emqx_access_rule_SUITE.erl b/test/emqx_access_rule_SUITE.erl index 36f887403..667df5bc2 100644 --- a/test/emqx_access_rule_SUITE.erl +++ b/test/emqx_access_rule_SUITE.erl @@ -29,9 +29,9 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_compile(_) -> - error('TODO'). +% t_compile(_) -> +% error('TODO'). -t_match(_) -> - error('TODO'). +% t_match(_) -> +% error('TODO'). diff --git a/test/emqx_acl_cache_SUITE.erl b/test/emqx_acl_cache_SUITE.erl index 67fde1df0..934f3de2a 100644 --- a/test/emqx_acl_cache_SUITE.erl +++ b/test/emqx_acl_cache_SUITE.erl @@ -57,39 +57,39 @@ t_clean_acl_cache(_Config) -> ?assertEqual(0, length(gen_server:call(ClientPid, list_acl_cache))), emqtt:stop(Client). -t_cache_k(_) -> - error('TODO'). +% t_cache_k(_) -> +% error('TODO'). -t_cache_v(_) -> - error('TODO'). +% t_cache_v(_) -> +% error('TODO'). -t_cleanup_acl_cache(_) -> - error('TODO'). +% t_cleanup_acl_cache(_) -> +% error('TODO'). -t_get_oldest_key(_) -> - error('TODO'). +% t_get_oldest_key(_) -> +% error('TODO'). -t_get_newest_key(_) -> - error('TODO'). +% t_get_newest_key(_) -> +% error('TODO'). -t_get_cache_max_size(_) -> - error('TODO'). +% t_get_cache_max_size(_) -> +% error('TODO'). -t_get_cache_size(_) -> - error('TODO'). +% t_get_cache_size(_) -> +% error('TODO'). -t_dump_acl_cache(_) -> - error('TODO'). +% t_dump_acl_cache(_) -> +% error('TODO'). -t_empty_acl_cache(_) -> - error('TODO'). +% t_empty_acl_cache(_) -> +% error('TODO'). -t_put_acl_cache(_) -> - error('TODO'). +% t_put_acl_cache(_) -> +% error('TODO'). -t_get_acl_cache(_) -> - error('TODO'). +% t_get_acl_cache(_) -> +% error('TODO'). -t_is_enabled(_) -> - error('TODO'). +% t_is_enabled(_) -> +% error('TODO'). diff --git a/test/emqx_base62_SUITE.erl b/test/emqx_base62_SUITE.erl index 413fe660b..83d11ae1c 100644 --- a/test/emqx_base62_SUITE.erl +++ b/test/emqx_base62_SUITE.erl @@ -24,13 +24,6 @@ all() -> emqx_ct:all(?MODULE). -t_encode(_) -> - error('TODO'). - -t_decode(_) -> - error('TODO'). - - t_proper_base62(_) -> Opts = [{numtests, 100}, {to_file, user}], ?assert(proper:quickcheck(prop_symmetric(), Opts)), diff --git a/test/emqx_batch_SUITE.erl b/test/emqx_batch_SUITE.erl index df9f7f778..e45e89c83 100644 --- a/test/emqx_batch_SUITE.erl +++ b/test/emqx_batch_SUITE.erl @@ -23,22 +23,6 @@ all() -> emqx_ct:all(?MODULE). - -t_init(_) -> - error('TODO'). - -t_push(_) -> - error('TODO'). - -t_commit(_) -> - error('TODO'). - -t_size(_) -> - error('TODO'). - -t_items(_) -> - error('TODO'). - t_batch_full_commit(_) -> B0 = emqx_batch:init(#{batch_size => 3, linger_ms => 2000, diff --git a/test/emqx_cm_SUITE.erl b/test/emqx_cm_SUITE.erl index 0e13bd785..d0c2dde96 100644 --- a/test/emqx_cm_SUITE.erl +++ b/test/emqx_cm_SUITE.erl @@ -89,26 +89,26 @@ t_lock_clientid(_) -> {true, _Nodes} = emqx_cm_locker:unlock(<<"clientid">>). -t_unregister_channel(_) -> - error('TODO'). +% t_unregister_channel(_) -> +% error('TODO'). -t_get_chan_attrs(_) -> - error('TODO'). +% t_get_chan_attrs(_) -> +% error('TODO'). -t_get_chan_stats(_) -> - error('TODO'). +% t_get_chan_stats(_) -> +% error('TODO'). -t_lookup_channels(_) -> - error('TODO'). +% t_lookup_channels(_) -> +% error('TODO'). -t_set_chan_stats(_) -> - error('TODO'). +% t_set_chan_stats(_) -> +% error('TODO'). -t_set_chan_attrs(_) -> - error('TODO'). +% t_set_chan_attrs(_) -> +% error('TODO'). -t_register_channel(_) -> - error('TODO'). +% t_register_channel(_) -> +% error('TODO'). -t_stats_fun(_) -> - error('TODO'). +% t_stats_fun(_) -> +% error('TODO'). diff --git a/test/emqx_cm_locker_SUITE.erl b/test/emqx_cm_locker_SUITE.erl index 937305c23..f476c99ac 100644 --- a/test/emqx_cm_locker_SUITE.erl +++ b/test/emqx_cm_locker_SUITE.erl @@ -29,15 +29,15 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_start_link(_) -> - error('TODO'). +% t_start_link(_) -> +% error('TODO'). -t_trans(_) -> - error('TODO'). +% t_trans(_) -> +% error('TODO'). -t_lock(_) -> - error('TODO'). +% t_lock(_) -> +% error('TODO'). -t_unlock(_) -> - error('TODO'). +% t_unlock(_) -> +% error('TODO'). diff --git a/test/emqx_flapping_SUITE.erl b/test/emqx_flapping_SUITE.erl index 430edcc3a..a9234c541 100644 --- a/test/emqx_flapping_SUITE.erl +++ b/test/emqx_flapping_SUITE.erl @@ -38,14 +38,6 @@ set_special_configs(_App) -> ok. end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]), ok. - - -t_check(_) -> - error('TODO'). - -t_detect(_) -> - error('TODO'). - t_detect_check(_) -> ClientInfo = #{zone => external, diff --git a/test/emqx_guid_SUITE.erl b/test/emqx_guid_SUITE.erl index 56fcd8aaf..eec808ae2 100644 --- a/test/emqx_guid_SUITE.erl +++ b/test/emqx_guid_SUITE.erl @@ -39,26 +39,3 @@ t_guid_hexstr(_) -> t_guid_base62(_) -> Guid = emqx_guid:gen(), ?assertEqual(Guid, emqx_guid:from_base62(emqx_guid:to_base62(Guid))). - -t_new(_) -> - error('TODO'). - -t_timestamp(_) -> - error('TODO'). - -t_to_hexstr(_) -> - error('TODO'). - -t_from_hexstr(_) -> - error('TODO'). - -t_from_base62(_) -> - error('TODO'). - -t_to_base62(_) -> - error('TODO'). - -t_gen(_) -> - error('TODO'). - - diff --git a/test/emqx_hooks_SUITE.erl b/test/emqx_hooks_SUITE.erl index e5d04bd6d..b5670ecc1 100644 --- a/test/emqx_hooks_SUITE.erl +++ b/test/emqx_hooks_SUITE.erl @@ -23,25 +23,21 @@ all() -> emqx_ct:all(?MODULE). +% t_lookup(_) -> +% error('TODO'). +% t_run_fold(_) -> +% error('TODO'). -t_lookup(_) -> - error('TODO'). +% t_run(_) -> +% error('TODO'). +% t_del(_) -> +% error('TODO'). -t_run_fold(_) -> - error('TODO'). - -t_run(_) -> - error('TODO'). - -t_del(_) -> - error('TODO'). - -t_add(_) -> - error('TODO'). +% t_add(_) -> +% error('TODO'). - t_add_del_hook(_) -> {ok, _} = emqx_hooks:start_link(), ok = emqx:hook(test_hook, fun ?MODULE:hook_fun1/1, []), diff --git a/test/emqx_inflight_SUITE.erl b/test/emqx_inflight_SUITE.erl index f441b0f81..21b8350de 100644 --- a/test/emqx_inflight_SUITE.erl +++ b/test/emqx_inflight_SUITE.erl @@ -23,10 +23,6 @@ -include_lib("emqx_ct_helpers/include/emqx_ct.hrl"). all() -> emqx_ct:all(?MODULE). - - -t_new(_) -> - error('TODO'). t_contain(_) -> Inflight = emqx_inflight:insert(k, v, emqx_inflight:new()), @@ -94,12 +90,5 @@ t_window(_) -> a, 1, emqx_inflight:new(2))), ?assertEqual([a, b], emqx_inflight:window(Inflight)). -t_to_list(_) -> - error('TODO'). - -t_size(_) -> - error('TODO'). - -t_max_size(_) -> - error('TODO'). - +% t_to_list(_) -> +% error('TODO'). diff --git a/test/emqx_keepalive_SUITE.erl b/test/emqx_keepalive_SUITE.erl index 58c3d0af0..0bdc79f60 100644 --- a/test/emqx_keepalive_SUITE.erl +++ b/test/emqx_keepalive_SUITE.erl @@ -23,13 +23,6 @@ all() -> emqx_ct:all(?MODULE). - -t_init(_) -> - error('TODO'). - -t_info(_) -> - error('TODO'). - t_check(_) -> Keepalive = emqx_keepalive:init(60), ?assertEqual(60, emqx_keepalive:info(interval, Keepalive)), diff --git a/test/emqx_logger_SUITE.erl b/test/emqx_logger_SUITE.erl index 4b0dc2c9a..0327204cb 100644 --- a/test/emqx_logger_SUITE.erl +++ b/test/emqx_logger_SUITE.erl @@ -82,8 +82,8 @@ t_set_log_level(_) -> ?assertMatch({error, _Error}, ?LOGGER:set_log_level(for_test)), ?assertEqual(ok, ?LOGGER:set_log_level(debug)). -t_parse_transform(_) -> - error('TODO'). +% t_parse_transform(_) -> +% error('TODO'). t_set_metadata_peername(_) -> ?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")). diff --git a/test/emqx_message_SUITE.erl b/test/emqx_message_SUITE.erl index 160b15aa1..94a0fb1f6 100644 --- a/test/emqx_message_SUITE.erl +++ b/test/emqx_message_SUITE.erl @@ -91,9 +91,6 @@ t_undefined_headers(_) -> ?assertEqual(1, emqx_message:get_header(a, Msg1)), Msg2 = emqx_message:set_header(c, 3, Msg), ?assertEqual(3, emqx_message:get_header(c, Msg2)). - -t_remove_header(_) -> - error('TODO'). t_format(_) -> Msg = emqx_message:make(<<"clientid">>, <<"topic">>, <<"payload">>), @@ -118,9 +115,8 @@ t_is_expired(_) -> Msg2 = emqx_message:update_expiry(Msg1), ?assertEqual(1, emqx_message:get_header('Message-Expiry-Interval', Msg2)). - -t_to_list(_) -> - error('TODO'). +% t_to_list(_) -> +% error('TODO'). t_to_packet(_) -> Pkt = #mqtt_packet{header = #mqtt_packet_header{type = ?PUBLISH, @@ -146,16 +142,3 @@ t_to_map(_) -> {timestamp, emqx_message:timestamp(Msg)}], ?assertEqual(List, emqx_message:to_list(Msg)), ?assertEqual(maps:from_list(List), emqx_message:to_map(Msg)). - -t_update_expiry(_) -> - error('TODO'). - -t_set_header(_) -> - error('TODO'). - -t_set_flag(_) -> - error('TODO'). - -t_set_headers(_) -> - error('TODO'). - diff --git a/test/emqx_mod_acl_internal_SUITE.erl b/test/emqx_mod_acl_internal_SUITE.erl index 0cd9c1cd8..05b406f7a 100644 --- a/test/emqx_mod_acl_internal_SUITE.erl +++ b/test/emqx_mod_acl_internal_SUITE.erl @@ -29,18 +29,18 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_load(_) -> - error('TODO'). +% t_load(_) -> +% error('TODO'). -t_unload(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). -t_all_rules(_) -> - error('TODO'). +% t_all_rules(_) -> +% error('TODO'). -t_check_acl(_) -> - error('TODO'). +% t_check_acl(_) -> +% error('TODO'). -t_reload_acl(_) -> - error('TODO'). +% t_reload_acl(_) -> +% error('TODO'). diff --git a/test/emqx_mod_presence_SUITE.erl b/test/emqx_mod_presence_SUITE.erl index a5fba303e..abc145a2d 100644 --- a/test/emqx_mod_presence_SUITE.erl +++ b/test/emqx_mod_presence_SUITE.erl @@ -29,16 +29,16 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_load(_) -> - error('TODO'). +% t_load(_) -> +% error('TODO'). -t_unload(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). -t_on_client_connected(_) -> - error('TODO'). +% t_on_client_connected(_) -> +% error('TODO'). -t_on_client_disconnected(_) -> - error('TODO'). +% t_on_client_disconnected(_) -> +% error('TODO'). diff --git a/test/emqx_mod_rewrite_SUITE.erl b/test/emqx_mod_rewrite_SUITE.erl index dcc0c6a6b..7ed1cad60 100644 --- a/test/emqx_mod_rewrite_SUITE.erl +++ b/test/emqx_mod_rewrite_SUITE.erl @@ -29,18 +29,18 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_load(_) -> - error('TODO'). +% t_load(_) -> +% error('TODO'). -t_rewrite_subscribe(_) -> - error('TODO'). +% t_rewrite_subscribe(_) -> +% error('TODO'). -t_rewrite_unsubscribe(_) -> - error('TODO'). +% t_rewrite_unsubscribe(_) -> +% error('TODO'). -t_rewrite_publish(_) -> - error('TODO'). +% t_rewrite_publish(_) -> +% error('TODO'). -t_unload(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). diff --git a/test/emqx_mod_subscription_SUITE.erl b/test/emqx_mod_subscription_SUITE.erl index 026bc976a..ed5acc3d6 100644 --- a/test/emqx_mod_subscription_SUITE.erl +++ b/test/emqx_mod_subscription_SUITE.erl @@ -29,12 +29,12 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_load(_) -> - error('TODO'). +% t_load(_) -> +% error('TODO'). -t_on_client_connected(_) -> - error('TODO'). +% t_on_client_connected(_) -> +% error('TODO'). -t_unload(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). diff --git a/test/emqx_modules_SUITE.erl b/test/emqx_modules_SUITE.erl index 886a8d101..f4d18ddd7 100644 --- a/test/emqx_modules_SUITE.erl +++ b/test/emqx_modules_SUITE.erl @@ -52,12 +52,11 @@ end_per_suite(_Config) -> %% Test cases %%-------------------------------------------------------------------- -t_unload(_) -> - error('TODO'). - -t_load(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). +% t_load(_) -> +% error('TODO'). %% Test case for emqx_mod_presence t_mod_presence(_) -> diff --git a/test/emqx_mqtt_caps_SUITE.erl b/test/emqx_mqtt_caps_SUITE.erl index 432525bd1..40f5d44d9 100644 --- a/test/emqx_mqtt_caps_SUITE.erl +++ b/test/emqx_mqtt_caps_SUITE.erl @@ -24,13 +24,11 @@ all() -> emqx_ct:all(?MODULE). +% t_get_caps(_) -> +% error('TODO'). -t_get_caps(_) -> - error('TODO'). - -t_default(_) -> - error('TODO'). - +% t_default(_) -> +% error('TODO'). t_check_pub(_) -> PubCaps = #{max_qos_allowed => ?QOS_1, diff --git a/test/emqx_mqtt_props_SUITE.erl b/test/emqx_mqtt_props_SUITE.erl index ef4876671..53ef76d4c 100644 --- a/test/emqx_mqtt_props_SUITE.erl +++ b/test/emqx_mqtt_props_SUITE.erl @@ -79,11 +79,11 @@ foreach_prop(Fun) -> lists:foreach(Fun, maps:to_list(emqx_mqtt_props:all())). -t_all(_) -> - error('TODO'). +% t_all(_) -> +% error('TODO'). -t_set(_) -> - error('TODO'). +% t_set(_) -> +% error('TODO'). -t_get(_) -> - error('TODO'). \ No newline at end of file +% t_get(_) -> +% error('TODO'). \ No newline at end of file diff --git a/test/emqx_mqueue_SUITE.erl b/test/emqx_mqueue_SUITE.erl index c33f415a8..a421b6391 100644 --- a/test/emqx_mqueue_SUITE.erl +++ b/test/emqx_mqueue_SUITE.erl @@ -29,23 +29,23 @@ all() -> emqx_ct:all(?MODULE). -t_init(_) -> - error('TODO'). +% t_init(_) -> +% error('TODO'). -t_is_empty(_) -> - error('TODO'). +% t_is_empty(_) -> +% error('TODO'). -t_len(_) -> - error('TODO'). +% t_len(_) -> +% error('TODO'). -t_max_len(_) -> - error('TODO'). +% t_max_len(_) -> +% error('TODO'). -t_dropped(_) -> - error('TODO'). +% t_dropped(_) -> +% error('TODO'). -t_stats(_) -> - error('TODO'). +% t_stats(_) -> +% error('TODO'). t_in(_) -> Opts = #{max_len => 5, store_qos0 => true}, diff --git a/test/emqx_oom_SUITE.erl b/test/emqx_oom_SUITE.erl index c7cae0c36..a44d32d2c 100644 --- a/test/emqx_oom_SUITE.erl +++ b/test/emqx_oom_SUITE.erl @@ -42,7 +42,6 @@ t_check(_) -> [self() ! {msg, I} || I <- lists:seq(1, 6)], ?assertEqual({shutdown, message_queue_too_long}, emqx_oom:check(Oom)). - -t_info(_) -> - error('TODO'). +% t_info(_) -> +% error('TODO'). diff --git a/test/emqx_os_mon_SUITE.erl b/test/emqx_os_mon_SUITE.erl index 63ba421f6..5f320f64b 100644 --- a/test/emqx_os_mon_SUITE.erl +++ b/test/emqx_os_mon_SUITE.erl @@ -29,43 +29,15 @@ init_per_suite(Config) -> end_per_suite(_Config) -> application:stop(os_mon). - - -t_get_cpu_check_interval(_) -> - error('TODO'). -t_set_cpu_check_interval(_) -> - error('TODO'). +% t_set_mem_check_interval(_) -> +% error('TODO'). -t_get_cpu_high_watermark(_) -> - error('TODO'). +% t_set_sysmem_high_watermark(_) -> +% error('TODO'). -t_set_cpu_high_watermark(_) -> - error('TODO'). - -t_get_cpu_low_watermark(_) -> - error('TODO'). - -t_set_cpu_low_watermark(_) -> - error('TODO'). - -t_get_mem_check_interval(_) -> - error('TODO'). - -t_set_mem_check_interval(_) -> - error('TODO'). - -t_get_sysmem_high_watermark(_) -> - error('TODO'). - -t_set_sysmem_high_watermark(_) -> - error('TODO'). - -t_get_procmem_high_watermark(_) -> - error('TODO'). - -t_set_procmem_high_watermark(_) -> - error('TODO'). +% t_set_procmem_high_watermark(_) -> +% error('TODO'). t_api(_) -> gen_event:swap_handler(alarm_handler, {emqx_alarm_handler, swap}, {alarm_handler, []}), diff --git a/test/emqx_pd_SUITE.erl b/test/emqx_pd_SUITE.erl index 5e86b42f4..7c08d7f18 100644 --- a/test/emqx_pd_SUITE.erl +++ b/test/emqx_pd_SUITE.erl @@ -23,12 +23,6 @@ all() -> emqx_ct:all(?MODULE). -t_get_counter(_) -> - error('TODO'). - -t_reset_counter(_) -> - error('TODO'). - t_update_counter(_) -> ?assertEqual(undefined, emqx_pd:update_counter(bytes, 1)), ?assertEqual(1, emqx_pd:update_counter(bytes, 1)), diff --git a/test/emqx_plugins_SUITE.erl b/test/emqx_plugins_SUITE.erl index c4a962689..402a0887a 100644 --- a/test/emqx_plugins_SUITE.erl +++ b/test/emqx_plugins_SUITE.erl @@ -43,22 +43,20 @@ init_per_suite(Config) -> Config. - -t_load_expand_plugin(_) -> - error('TODO'). +% t_load_expand_plugin(_) -> +% error('TODO'). -t_list(_) -> - error('TODO'). +% t_list(_) -> +% error('TODO'). -t_find_plugin(_) -> - error('TODO'). +% t_find_plugin(_) -> +% error('TODO'). -t_unload(_) -> - error('TODO'). +% t_unload(_) -> +% error('TODO'). - -t_init(_) -> - error('TODO'). +% t_init(_) -> +% error('TODO'). set_sepecial_cfg(_) -> ExpandPath = filename:dirname(code:lib_dir(emqx_mini_plugin)), diff --git a/test/emqx_pmon_SUITE.erl b/test/emqx_pmon_SUITE.erl index 2fcfa4be9..051c2ae6f 100644 --- a/test/emqx_pmon_SUITE.erl +++ b/test/emqx_pmon_SUITE.erl @@ -23,11 +23,11 @@ all() -> emqx_ct:all(?MODULE). -t_new(_) -> - error('TODO'). +% t_new(_) -> +% error('TODO'). -t_count(_) -> - error('TODO'). +% t_count(_) -> +% error('TODO'). t_monitor(_) -> PMon = emqx_pmon:new(), @@ -37,8 +37,8 @@ t_monitor(_) -> PMon2 = emqx_pmon:demonitor(self(), PMon2), ?assertEqual(0, emqx_pmon:count(PMon2)). -t_demonitor(_) -> - error('TODO'). +% t_demonitor(_) -> +% error('TODO'). t_find(_) -> PMon = emqx_pmon:new(), @@ -60,5 +60,5 @@ t_erase(_) -> ?assertEqual([{self(), val}], Items), ?assertEqual(0, emqx_pmon:count(PMon3)). -t_erase_all(_) -> - error('TODO'). +% t_erase_all(_) -> +% error('TODO'). diff --git a/test/emqx_pool_SUITE.erl b/test/emqx_pool_SUITE.erl index 06d0f2917..f9c481ac1 100644 --- a/test/emqx_pool_SUITE.erl +++ b/test/emqx_pool_SUITE.erl @@ -86,6 +86,5 @@ t_unexpected(_) -> test_mfa() -> lists:foldl(fun(X, Sum) -> X + Sum end, 0, [1,2,3,4,5]). - -t_async_submit(_) -> - error('TODO'). +% t_async_submit(_) -> +% error('TODO'). diff --git a/test/emqx_reason_codes_SUITE.erl b/test/emqx_reason_codes_SUITE.erl index b2db5fe42..2d50258ed 100644 --- a/test/emqx_reason_codes_SUITE.erl +++ b/test/emqx_reason_codes_SUITE.erl @@ -25,24 +25,23 @@ all() -> emqx_ct:all(?MODULE). +% t_name(_) -> +% error('TODO'). -t_name(_) -> - error('TODO'). +% t_text(_) -> +% error('TODO'). -t_text(_) -> - error('TODO'). +% t_mqtt_frame_error(_) -> +% error('TODO'). -t_mqtt_frame_error(_) -> - error('TODO'). +% t_connack_error(_) -> +% error('TODO'). -t_connack_error(_) -> - error('TODO'). +% t_compat(_) -> +% error('TODO'). -t_compat(_) -> - error('TODO'). - -t_formalized(_) -> - error('TODO'). +% t_formalized(_) -> +% error('TODO'). t_prop_name_text(_) -> ?assert(proper:quickcheck(prop_name_text(), prop_name_text(opts))). diff --git a/test/emqx_router_SUITE.erl b/test/emqx_router_SUITE.erl index 46d669d9c..9e4cbb240 100644 --- a/test/emqx_router_SUITE.erl +++ b/test/emqx_router_SUITE.erl @@ -45,23 +45,23 @@ t_mnesia(_) -> %% for coverage ok = emqx_router:mnesia(copy). -t_add_route(_) -> - error('TODO'). +% t_add_route(_) -> +% error('TODO'). -t_do_add_route(_) -> - error('TODO'). +% t_do_add_route(_) -> +% error('TODO'). -t_lookup_routes(_) -> - error('TODO'). +% t_lookup_routes(_) -> +% error('TODO'). -t_delete_route(_) -> - error('TODO'). +% t_delete_route(_) -> +% error('TODO'). -t_do_delete_route(_) -> - error('TODO'). +% t_do_delete_route(_) -> +% error('TODO'). -t_topics(_) -> - error('TODO'). +% t_topics(_) -> +% error('TODO'). t_add_delete(_) -> ?R:add_route(<<"a/b/c">>), diff --git a/test/emqx_router_helper_SUITE.erl b/test/emqx_router_helper_SUITE.erl index dbf13565b..55bb6c5d7 100644 --- a/test/emqx_router_helper_SUITE.erl +++ b/test/emqx_router_helper_SUITE.erl @@ -29,13 +29,12 @@ init_per_testcase(_TestCase, Config) -> end_per_testcase(_TestCase, Config) -> Config. -t_mnesia(_) -> - error('TODO'). +% t_mnesia(_) -> +% error('TODO'). +% t_monitor(_) -> +% error('TODO'). -t_monitor(_) -> - error('TODO'). - -t_stats_fun(_) -> - error('TODO'). +% t_stats_fun(_) -> +% error('TODO'). diff --git a/test/emqx_sequence_SUITE.erl b/test/emqx_sequence_SUITE.erl index 4a6ae3463..a12837492 100644 --- a/test/emqx_sequence_SUITE.erl +++ b/test/emqx_sequence_SUITE.erl @@ -29,22 +29,20 @@ all() -> emqx_ct:all(?MODULE). +% t_currval(_) -> +% error('TODO'). -t_currval(_) -> - error('TODO'). +% t_delete(_) -> +% error('TODO'). -t_delete(_) -> - error('TODO'). +% t_create(_) -> +% error('TODO'). -t_create(_) -> - error('TODO'). - -t_reclaim(_) -> - error('TODO'). - -t_nextval(_) -> - error('TODO'). +% t_reclaim(_) -> +% error('TODO'). +% t_nextval(_) -> +% error('TODO'). t_generate(_) -> ok = emqx_sequence:create(seqtab), diff --git a/test/emqx_shared_sub_SUITE.erl b/test/emqx_shared_sub_SUITE.erl index 07a9137b0..23b282832 100644 --- a/test/emqx_shared_sub_SUITE.erl +++ b/test/emqx_shared_sub_SUITE.erl @@ -39,20 +39,20 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([]). - t_is_ack_required(_) -> - error('TODO'). +% t_is_ack_required(_) -> +% error('TODO'). -t_maybe_nack_dropped(_) -> - error('TODO'). +% t_maybe_nack_dropped(_) -> +% error('TODO'). -t_nack_no_connection(_) -> - error('TODO'). +% t_nack_no_connection(_) -> +% error('TODO'). -t_maybe_ack(_) -> - error('TODO'). +% t_maybe_ack(_) -> +% error('TODO'). -t_subscribers(_) -> - error('TODO'). +% t_subscribers(_) -> +% error('TODO'). t_random_basic(_) -> ok = ensure_config(random), @@ -239,15 +239,14 @@ last_message(ExpectedPayload, Pids) -> <<"not yet?">> end. -t_dispatch(_) -> - error('TODO'). +% t_dispatch(_) -> +% error('TODO'). -t_unsubscribe(_) -> - error('TODO'). - -t_subscribe(_) -> - error('TODO'). +% t_unsubscribe(_) -> +% error('TODO'). +% t_subscribe(_) -> +% error('TODO'). %%-------------------------------------------------------------------- %% help functions diff --git a/test/emqx_sys_SUITE.erl b/test/emqx_sys_SUITE.erl index d9942fc07..ff8645936 100644 --- a/test/emqx_sys_SUITE.erl +++ b/test/emqx_sys_SUITE.erl @@ -43,27 +43,26 @@ end_per_suite(_Config) -> ok = emqx_logger:set_log_level(error), ok. - t_version(_) -> - error('TODO'). +% t_version(_) -> +% error('TODO'). -t_sysdescr(_) -> - error('TODO'). +% t_sysdescr(_) -> +% error('TODO'). -t_uptime(_) -> - error('TODO'). +% t_uptime(_) -> +% error('TODO'). -t_datetime(_) -> - error('TODO'). +% t_datetime(_) -> +% error('TODO'). -t_sys_interval(_) -> - error('TODO'). +% t_sys_interval(_) -> +% error('TODO'). -t_sys_heatbeat_interval(_) -> - error('TODO'). - -t_info(_) -> - error('TODO'). +% t_sys_heatbeat_interval(_) -> +% error('TODO'). +% t_info(_) -> +% error('TODO'). t_prop_sys(_) -> Opts = [{numtests, 100}, {to_file, user}], From 3e7014351836674011cd92d7a9d3ed8b42eb66a1 Mon Sep 17 00:00:00 2001 From: zhanghongtong Date: Fri, 1 Nov 2019 17:07:32 +0800 Subject: [PATCH 32/40] Fix error test case for emqx --- test/emqx_SUITE.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/emqx_SUITE.erl b/test/emqx_SUITE.erl index 1ea78a446..b612b100b 100644 --- a/test/emqx_SUITE.erl +++ b/test/emqx_SUITE.erl @@ -45,7 +45,6 @@ t_stop_start(_) -> false = emqx:is_running(node()). t_get_env(_) -> - emqx:start(), ?assertEqual(undefined, emqx:get_env(undefined_key)), ?assertEqual(default_value, emqx:get_env(undefined_key, default_value)), application:set_env(emqx, undefined_key, hello), @@ -54,6 +53,7 @@ t_get_env(_) -> application:unset_env(emqx, undefined_key). t_emqx_pubsub_api(_) -> + emqx:start(), true = emqx:is_running(node()), {ok, C} = emqtt:start_link([{host, "localhost"}, {clientid, "myclient"}]), {ok, _} = emqtt:connect(C), From 0f2609b56de8332225b00d27a1916e96b52776b5 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 17:36:56 +0800 Subject: [PATCH 33/40] Add test case for logger parse transform --- test/emqx_logger_SUITE.erl | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/test/emqx_logger_SUITE.erl b/test/emqx_logger_SUITE.erl index 4b0dc2c9a..58380be48 100644 --- a/test/emqx_logger_SUITE.erl +++ b/test/emqx_logger_SUITE.erl @@ -22,6 +22,14 @@ -include_lib("eunit/include/eunit.hrl"). -define(LOGGER, emqx_logger). +-define(a, "a"). + +-define(PARSE_TRANS_TEST_CODE, + "-module(mytest).\n" + "-logger_header(\"[MyTest]\").\n" + "-export([run/0]).\n" + "-compile({parse_transform, logger_header}).\n" + "run() -> '$logger_header'()."). all() -> emqx_ct:all(?MODULE). @@ -83,7 +91,19 @@ t_set_log_level(_) -> ?assertEqual(ok, ?LOGGER:set_log_level(debug)). t_parse_transform(_) -> - error('TODO'). + {ok, Toks, _EndLine} = erl_scan:string(?PARSE_TRANS_TEST_CODE), + FormToks = split_toks_at_dot(Toks), + Forms = [case erl_parse:parse_form(Ts) of + {ok, Form} -> + Form; + {error, Reason} -> + erlang:error({parse_form_error, Ts, Reason}) + end + || Ts <- FormToks], + ct:log("=====: ~p", [Forms]), + AST = emqx_logger:parse_transform(Forms, []), + ct:log("=====: ~p", [AST]), + ?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST)). t_set_metadata_peername(_) -> ?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")). @@ -91,3 +111,13 @@ t_set_metadata_peername(_) -> t_set_metadata_clientid(_) -> ?assertEqual(ok, ?LOGGER:set_metadata_clientid(<<>>)), ?assertEqual(ok, ?LOGGER:set_metadata_clientid("for_test")). + + +split_toks_at_dot(AllToks) -> + case lists:splitwith(fun is_no_dot/1, AllToks) of + {Toks, [{dot,_}=Dot]} -> [Toks ++ [Dot]]; + {Toks, [{dot,_}=Dot | Tl]} -> [Toks ++ [Dot] | split_toks_at_dot(Tl)] + end. + +is_no_dot({dot,_}) -> false; +is_no_dot(_) -> true. From d946848d42dbea6f90aad4522640c5d2a1df6e94 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 18:21:46 +0800 Subject: [PATCH 34/40] Improve testcases for emqx_logger --- src/emqx_logger.erl | 1 + test/emqx_logger_SUITE.erl | 31 ++++++++++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/emqx_logger.erl b/src/emqx_logger.erl index 0cca94e21..cc167591e 100644 --- a/src/emqx_logger.erl +++ b/src/emqx_logger.erl @@ -43,6 +43,7 @@ , set_primary_log_level/1 , set_log_handler_level/2 , set_log_level/1 + , set_all_log_handlers_level/1 ]). -export([ get_primary_log_level/0 diff --git a/test/emqx_logger_SUITE.erl b/test/emqx_logger_SUITE.erl index 58380be48..053b04fc6 100644 --- a/test/emqx_logger_SUITE.erl +++ b/test/emqx_logger_SUITE.erl @@ -31,6 +31,12 @@ "-compile({parse_transform, logger_header}).\n" "run() -> '$logger_header'()."). +-define(PARSE_TRANS_TEST_CODE2, + "-module(mytest).\n" + "-export([run/0]).\n" + "-compile({parse_transform, logger_header}).\n" + "run() -> '$logger_header'()."). + all() -> emqx_ct:all(?MODULE). init_per_testcase(_TestCase, Config) -> @@ -90,6 +96,9 @@ t_set_log_level(_) -> ?assertMatch({error, _Error}, ?LOGGER:set_log_level(for_test)), ?assertEqual(ok, ?LOGGER:set_log_level(debug)). +t_set_all_log_handlers_level(_) -> + ?assertMatch({error, _Error}, ?LOGGER:set_all_log_handlers_level(for_test)). + t_parse_transform(_) -> {ok, Toks, _EndLine} = erl_scan:string(?PARSE_TRANS_TEST_CODE), FormToks = split_toks_at_dot(Toks), @@ -101,9 +110,25 @@ t_parse_transform(_) -> end || Ts <- FormToks], ct:log("=====: ~p", [Forms]), - AST = emqx_logger:parse_transform(Forms, []), - ct:log("=====: ~p", [AST]), - ?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST)). + AST1 = emqx_logger:parse_transform(Forms, []), + ct:log("=====: ~p", [AST1]), + ?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST1)). + +t_parse_transform_empty_header(_) -> + {ok, Toks, _EndLine} = erl_scan:string(?PARSE_TRANS_TEST_CODE2), + FormToks = split_toks_at_dot(Toks), + Forms = [case erl_parse:parse_form(Ts) of + {ok, Form} -> + Form; + {error, Reason} -> + erlang:error({parse_form_error, Ts, Reason}) + end + || Ts <- FormToks], + ct:log("=====: ~p", [Forms]), + AST2 = emqx_logger:parse_transform(Forms++[{eof, 15}], []), + ct:log("=====: ~p", [AST2]), + ?assertNotEqual(false, lists:keyfind('$logger_header', 3, AST2)). + t_set_metadata_peername(_) -> ?assertEqual(ok, ?LOGGER:set_metadata_peername("for_test")). From 0be06a93d6ce382b4341ac9ca01852262f6fb36e Mon Sep 17 00:00:00 2001 From: zhouzb Date: Fri, 1 Nov 2019 18:38:30 +0800 Subject: [PATCH 35/40] Fix test cases --- src/emqx_time.erl | 51 ++++++++++++++ test/emqx_broker_helper_SUITE.erl | 9 +-- test/emqx_channel_SUITE.erl | 107 ++++++++++++++---------------- test/emqx_metrics_SUITE.erl | 2 +- test/emqx_pd_SUITE.erl | 6 +- test/emqx_time_SUITE.erl | 33 +++++++++ test/emqx_vm_SUITE.erl | 2 +- test/emqx_zone_SUITE.erl | 9 +-- 8 files changed, 140 insertions(+), 79 deletions(-) create mode 100644 src/emqx_time.erl create mode 100644 test/emqx_time_SUITE.erl diff --git a/src/emqx_time.erl b/src/emqx_time.erl new file mode 100644 index 000000000..211ea3d9e --- /dev/null +++ b/src/emqx_time.erl @@ -0,0 +1,51 @@ +%%-------------------------------------------------------------------- +%% 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_time). + +-export([ seed/0 + , now_secs/0 + , now_secs/1 + , now_ms/0 + , now_ms/1 + ]). + +-compile({inline, + [ seed/0 + , now_secs/0 + , now_secs/1 + , now_ms/0 + , now_ms/1 + ]}). + +seed() -> + rand:seed(exsplus, erlang:timestamp()). + +-spec(now_secs() -> pos_integer()). +now_secs() -> + erlang:system_time(second). + +-spec(now_secs(erlang:timestamp()) -> pos_integer()). +now_secs({MegaSecs, Secs, _MicroSecs}) -> + MegaSecs * 1000000 + Secs. + +-spec(now_ms() -> pos_integer()). +now_ms() -> + erlang:system_time(millisecond). + +-spec(now_ms(erlang:timestamp()) -> pos_integer()). +now_ms({MegaSecs, Secs, MicroSecs}) -> + (MegaSecs * 1000000 + Secs) * 1000 + round(MicroSecs/1000). \ No newline at end of file diff --git a/test/emqx_broker_helper_SUITE.erl b/test/emqx_broker_helper_SUITE.erl index 4e287f341..aa41e2cd7 100644 --- a/test/emqx_broker_helper_SUITE.erl +++ b/test/emqx_broker_helper_SUITE.erl @@ -23,15 +23,8 @@ all() -> emqx_ct:all(?MODULE). -init_per_suite(Config) -> - emqx_ct_helpers:boot_modules(all), - emqx_ct_helpers:start_apps([]), - Config. - -end_per_suite(_Config) -> - emqx_ct_helpers:stop_apps([]). - init_per_testcase(_TestCase, Config) -> + emqx_broker_helper:start_link(), Config. end_per_testcase(_TestCase, Config) -> diff --git a/test/emqx_channel_SUITE.erl b/test/emqx_channel_SUITE.erl index f8a910a47..7887eeca3 100644 --- a/test/emqx_channel_SUITE.erl +++ b/test/emqx_channel_SUITE.erl @@ -94,18 +94,9 @@ t_chan_info(_) -> t_chan_attrs(_) -> #{conn_state := connected} = emqx_channel:attrs(channel()). -t_chan_stats(_) -> - [] = emqx_channel:stats(channel()). - t_chan_caps(_) -> Caps = emqx_channel:caps(channel()). -t_chan_recvd(_) -> - _Channel = emqx_channel:recvd(10, channel()). - -t_chan_sent(_) -> - _Channel = emqx_channel:sent(10, channel()). - %%-------------------------------------------------------------------- %% Test cases for channel init %%-------------------------------------------------------------------- @@ -140,86 +131,86 @@ t_handle_in_qos0_publish(_) -> ok = meck:expect(emqx_broker, publish, fun(_) -> ok end), Channel = channel(#{conn_state => connected}), Publish = ?PUBLISH_PACKET(?QOS_0, <<"topic">>, undefined, <<"payload">>), - {ok, NChannel} = emqx_channel:handle_in(Publish, Channel), - ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, _NChannel} = emqx_channel:handle_in(Publish, Channel). + % ?assertEqual(#{publish_in => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_qos1_publish(_) -> ok = meck:expect(emqx_broker, publish, fun(_) -> ok end), Channel = channel(#{conn_state => connected}), Publish = ?PUBLISH_PACKET(?QOS_1, <<"topic">>, 1, <<"payload">>), - {ok, ?PUBACK_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), - ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), - ?assertEqual(#{publish_in => 1, puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, ?PUBACK_PACKET(1, RC), _NChannel} = emqx_channel:handle_in(Publish, Channel), + ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)). + % ?assertEqual(#{publish_in => 1, puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_qos2_publish(_) -> ok = meck:expect(emqx_session, publish, fun(_, _Msg, Session) -> {ok, [], Session} end), ok = meck:expect(emqx_session, info, fun(awaiting_rel_timeout, _Session) -> 300000 end), Channel = channel(#{conn_state => connected}), Publish = ?PUBLISH_PACKET(?QOS_2, <<"topic">>, 1, <<"payload">>), - {ok, ?PUBREC_PACKET(1, RC), NChannel} = emqx_channel:handle_in(Publish, Channel), - ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)), - ?assertEqual(#{publish_in => 1, pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, ?PUBREC_PACKET(1, RC), _NChannel} = emqx_channel:handle_in(Publish, Channel), + ?assert((RC == ?RC_SUCCESS) orelse (RC == ?RC_NO_MATCHING_SUBSCRIBERS)). + % ?assertEqual(#{publish_in => 1, pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_puback_ok(_) -> Msg = emqx_message:make(<<"t">>, <<"payload">>), ok = meck:expect(emqx_session, puback, fun(PacketId, Session) -> {ok, Msg, Session} end), Channel = channel(#{conn_state => connected}), - {ok, NChannel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, _NChannel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), Channel). + % ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_in_puback_id_in_use(_) -> ok = meck:expect(emqx_session, puback, fun(_, _Session) -> {error, ?RC_PACKET_IDENTIFIER_IN_USE} end), - {ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()), - ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). + {ok, _Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()). + % ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_puback_id_not_found(_) -> ok = meck:expect(emqx_session, puback, fun(_, _Session) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), - {ok, Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()), - ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). + {ok, _Channel} = emqx_channel:handle_in(?PUBACK_PACKET(1, ?RC_SUCCESS), channel()). + % ?assertEqual(#{puback_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrec_ok(_) -> Msg = emqx_message:make(test,?QOS_2, <<"t">>, <<"payload">>), ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {ok, Msg, Session} end), Channel = channel(#{conn_state => connected}), - {ok, ?PUBREL_PACKET(1, ?RC_SUCCESS), Channel1} - = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, - emqx_channel:info(pub_stats, Channel1)). + {ok, ?PUBREL_PACKET(1, ?RC_SUCCESS), _Channel1} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), Channel). + % ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + % emqx_channel:info(pub_stats, Channel1)). t_handle_in_pubrec_id_in_use(_) -> ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {error, ?RC_PACKET_IDENTIFIER_IN_USE} end), - {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), Channel} - = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()), - ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, - emqx_channel:info(pub_stats, Channel)). + {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_IN_USE), _Channel} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()). + % ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + % emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrec_id_not_found(_) -> ok = meck:expect(emqx_session, pubrec, fun(_, Session) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), - {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), Channel} - = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()), - ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, - emqx_channel:info(pub_stats, Channel)). + {ok, ?PUBREL_PACKET(1, ?RC_PACKET_IDENTIFIER_NOT_FOUND), _Channel} + = emqx_channel:handle_in(?PUBREC_PACKET(1, ?RC_SUCCESS), channel()). + % ?assertEqual(#{pubrec_in => 1, pubrel_out => 1}, + % emqx_channel:info(pub_stats, Channel)). t_handle_in_pubrel_ok(_) -> ok = meck:expect(emqx_session, pubrel, fun(_, Session) -> {ok, Session} end), Channel = channel(#{conn_state => connected}), - {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel1} - = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, - emqx_channel:info(pub_stats, Channel1)). + {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), _Channel1} + = emqx_channel:handle_in(?PUBREL_PACKET(1, ?RC_SUCCESS), Channel). + % ?assertEqual(#{pubrel_in => 1, pubcomp_out => 1}, + % emqx_channel:info(pub_stats, Channel1)). t_handle_in_pubrel_not_found_error(_) -> ok = meck:expect(emqx_session, pubrel, @@ -231,8 +222,8 @@ t_handle_in_pubrel_not_found_error(_) -> t_handle_in_pubcomp_ok(_) -> ok = meck:expect(emqx_session, pubcomp, fun(_, Session) -> {ok, Session} end), - {ok, Channel} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), channel()), - ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel)). + {ok, _Channel} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), channel()). + % ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_in_pubcomp_not_found_error(_) -> ok = meck:expect(emqx_session, pubcomp, @@ -240,8 +231,8 @@ t_handle_in_pubcomp_not_found_error(_) -> {error, ?RC_PACKET_IDENTIFIER_NOT_FOUND} end), Channel = channel(#{conn_state => connected}), - {ok, Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel), - ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)). + {ok, _Channel1} = emqx_channel:handle_in(?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel). + % ?assertEqual(#{pubcomp_in => 1}, emqx_channel:info(pub_stats, Channel1)). t_handle_in_subscribe(_) -> ok = meck:expect(emqx_session, subscribe, @@ -351,10 +342,10 @@ t_handle_out_publishes(_) -> Channel = channel(#{conn_state => connected}), Pub0 = {publish, undefined, emqx_message:make(<<"t">>, <<"qos0">>)}, Pub1 = {publish, 1, emqx_message:make(<<"c">>, ?QOS_1, <<"t">>, <<"qos1">>)}, - {ok, {outgoing, Packets}, NChannel} + {ok, {outgoing, Packets}, _NChannel} = emqx_channel:handle_out({publish, [Pub0, Pub1]}, Channel), - ?assertEqual(2, length(Packets)), - ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, NChannel)). + ?assertEqual(2, length(Packets)). + % ?assertEqual(#{publish_out => 2}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_publish(_) -> Msg = emqx_message:make(<<"clientid">>, ?QOS_1, <<"t">>, <<"payload">>), @@ -378,28 +369,28 @@ t_handle_out_connack_failure(_) -> t_handle_out_puback(_) -> Channel = channel(#{conn_state => connected}), - {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), NChannel} - = emqx_channel:handle_out(puback, {1, ?RC_SUCCESS}, Channel), - ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, ?PUBACK_PACKET(1, ?RC_SUCCESS), _NChannel} + = emqx_channel:handle_out(puback, {1, ?RC_SUCCESS}, Channel). + % ?assertEqual(#{puback_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_pubrec(_) -> Channel = channel(#{conn_state => connected}), - {ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), NChannel} - = emqx_channel:handle_out(pubrec, {1, ?RC_SUCCESS}, Channel), - ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). + {ok, ?PUBREC_PACKET(1, ?RC_SUCCESS), _NChannel} + = emqx_channel:handle_out(pubrec, {1, ?RC_SUCCESS}, Channel). + % ?assertEqual(#{pubrec_out => 1}, emqx_channel:info(pub_stats, NChannel)). t_handle_out_pubrel(_) -> Channel = channel(#{conn_state => connected}), {ok, ?PUBREL_PACKET(1), Channel1} = emqx_channel:handle_out(pubrel, {1, ?RC_SUCCESS}, Channel), - {ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), Channel2} - = emqx_channel:handle_out(pubrel, {2, ?RC_SUCCESS}, Channel1), - ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)). + {ok, ?PUBREL_PACKET(2, ?RC_SUCCESS), _Channel2} + = emqx_channel:handle_out(pubrel, {2, ?RC_SUCCESS}, Channel1). + % ?assertEqual(#{pubrel_out => 2}, emqx_channel:info(pub_stats, Channel2)). t_handle_out_pubcomp(_) -> - {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), Channel} - = emqx_channel:handle_out(pubcomp, {1, ?RC_SUCCESS}, channel()), - ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)). + {ok, ?PUBCOMP_PACKET(1, ?RC_SUCCESS), _Channel} + = emqx_channel:handle_out(pubcomp, {1, ?RC_SUCCESS}, channel()). + % ?assertEqual(#{pubcomp_out => 1}, emqx_channel:info(pub_stats, Channel)). t_handle_out_suback(_) -> {ok, ?SUBACK_PACKET(1, [?QOS_2]), _Channel} diff --git a/test/emqx_metrics_SUITE.erl b/test/emqx_metrics_SUITE.erl index a9445958b..f24cf3c3c 100644 --- a/test/emqx_metrics_SUITE.erl +++ b/test/emqx_metrics_SUITE.erl @@ -85,7 +85,7 @@ t_inc_recv(_) -> ok = emqx_metrics:inc_recv(?PACKET(?PINGREQ)), ok = emqx_metrics:inc_recv(?PACKET(?DISCONNECT)), ok = emqx_metrics:inc_recv(?PACKET(?AUTH)), - ignore = emqx_metrics:inc_recv(?PACKET(?RESERVED)), + ok = emqx_metrics:inc_recv(?PACKET(?RESERVED)), ?assertEqual(15, emqx_metrics:val('packets.received')), ?assertEqual(1, emqx_metrics:val('packets.connect.received')), ?assertEqual(4, emqx_metrics:val('messages.received')), diff --git a/test/emqx_pd_SUITE.erl b/test/emqx_pd_SUITE.erl index 7c08d7f18..25ede175d 100644 --- a/test/emqx_pd_SUITE.erl +++ b/test/emqx_pd_SUITE.erl @@ -24,9 +24,9 @@ all() -> emqx_ct:all(?MODULE). 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)), + ?assertEqual(undefined, emqx_pd:inc_counter(bytes, 1)), + ?assertEqual(1, emqx_pd:inc_counter(bytes, 1)), + ?assertEqual(2, emqx_pd:inc_counter(bytes, 1)), ?assertEqual(3, emqx_pd:get_counter(bytes)), ?assertEqual(3, emqx_pd:reset_counter(bytes)), ?assertEqual(0, emqx_pd:get_counter(bytes)). diff --git a/test/emqx_time_SUITE.erl b/test/emqx_time_SUITE.erl new file mode 100644 index 000000000..620fa1ee5 --- /dev/null +++ b/test/emqx_time_SUITE.erl @@ -0,0 +1,33 @@ +%%-------------------------------------------------------------------- +%% 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_time_SUITE). + +-compile(export_all). +-compile(nowarn_export_all). + +-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())). \ No newline at end of file diff --git a/test/emqx_vm_SUITE.erl b/test/emqx_vm_SUITE.erl index 0e38c91c4..1fa1cacbf 100644 --- a/test/emqx_vm_SUITE.erl +++ b/test/emqx_vm_SUITE.erl @@ -194,7 +194,7 @@ t_get_process_limit(_Config) -> emqx_vm:get_process_limit(). t_cpu_util(_Config) -> - ?assertEqual(0, emqx_vm:cpu_util()). + _Cpu = emqx_vm:cpu_util(). easy_server() -> {ok, LSock} = gen_tcp:listen(5678, [binary, {packet, 0}, {active, false}]), diff --git a/test/emqx_zone_SUITE.erl b/test/emqx_zone_SUITE.erl index 574b027ec..6b558b31d 100644 --- a/test/emqx_zone_SUITE.erl +++ b/test/emqx_zone_SUITE.erl @@ -101,11 +101,4 @@ t_uncovered_func(_) -> emqx_zone:stop(). t_frame_options(_) -> - ?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:frame_options(zone)). - -t_check_oom(_) -> - {ok, _} = emqx_zone:start_link(), - application:set_env(emqx, zones, [{zone, ?ENVS}]), - ok = emqx_zone:force_reload(), - ?assertEqual(ok, emqx_zone:check_oom(zone, fun() -> ok end)), - emqx_zone:stop(). \ No newline at end of file + ?assertMatch(#{strict_mode := _, max_size := _ }, emqx_zone:mqtt_frame_options(zone)). \ No newline at end of file From 7df8d0246c940b6884179e14f33194c36bce7319 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 18:39:46 +0800 Subject: [PATCH 36/40] Update esockd to v5.5.2 --- rebar.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.config b/rebar.config index 368666ff3..85811854a 100644 --- a/rebar.config +++ b/rebar.config @@ -2,7 +2,7 @@ [{jsx, "2.9.0"}, % hex {cowboy, "2.6.1"}, % hex {gproc, "0.8.0"}, % hex - {esockd, {git, "https://github.com/emqx/esockd", {tag, "v5.5.1"}}}, + {esockd, {git, "https://github.com/emqx/esockd", {tag, "v5.5.2"}}}, {ekka, {git, "https://github.com/emqx/ekka", {tag, "v0.6.2"}}}, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.4.1"}}}, {cuttlefish, {git, "https://github.com/emqx/cuttlefish", {tag, "v3.0.0"}}} From 865192c631a42eff95d83f172c43373ff863d0e7 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 19:57:14 +0800 Subject: [PATCH 37/40] Fix testcases for emqx_connection --- test/emqx_connection_SUITE.erl | 58 ++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/test/emqx_connection_SUITE.erl b/test/emqx_connection_SUITE.erl index 4ee48ecde..a0473dfc9 100644 --- a/test/emqx_connection_SUITE.erl +++ b/test/emqx_connection_SUITE.erl @@ -27,7 +27,14 @@ send_pend ]). -all() -> emqx_ct:all(?MODULE). +all() -> emqx_ct:all(?MODULE) ++ [{group, real_client}]. + +groups() -> + [{real_client, [non_parallel_tests], + [ + g_get_conn_stats, + g_handle_sock_passive + ]}]. %%-------------------------------------------------------------------- %% CT callbacks @@ -39,6 +46,16 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_group(real_client, Config) -> + emqx_ct_helpers:boot_modules(all), + emqx_ct_helpers:start_apps([]), + Config; +init_per_group(_, Config) -> Config. + +end_per_group(real_client, _Config) -> + emqx_ct_helpers:stop_apps([]); +end_per_group(_, Config) -> Config. + init_per_testcase(_TestCase, Config) -> %% Meck Transport ok = meck:new(emqx_transport, [non_strict, passthrough, no_history]), @@ -95,22 +112,19 @@ t_start_link_exit_on_activate(_) -> t_get_conn_info(_) -> with_connection(fun(CPid) -> #{sockinfo := SockInfo} = emqx_connection:info(CPid), - ?assertEqual(#{active_n => 100, - peername => {{127,0,0,1},3456}, - pub_limit => undefined, - rate_limit => undefined, - sockname => {{127,0,0,1},1883}, - sockstate => running, - socktype => tcp}, SockInfo) + ?assertEqual(#{active_n => 100,limiter => undefined, + peername => {{127,0,0,1},3456}, + sockname => {{127,0,0,1},1883}, + sockstate => running, + socktype => tcp}, SockInfo) end). -t_get_conn_stats(_) -> - with_connection(fun(CPid) -> +g_get_conn_stats(_) -> + with_client(fun(CPid) -> Stats = emqx_connection:stats(CPid), - lists:foreach(fun(Key) -> - 0 = proplists:get_value(Key, Stats) - end, ?STATS_KYES) - end). + ct:pal("==== stats: ~p", [Stats]), + [?assert(proplists:get_value(Key, Stats) >= 0) || Key <- ?STATS_KYES] + end, []). t_handle_call_discard(_) -> with_connection(fun(CPid) -> @@ -190,8 +204,8 @@ t_handle_sock_error(_) -> trap_exit(CPid, {shutdown, econnreset}) end, #{trap_exit => true}). -t_handle_sock_passive(_) -> - with_connection(fun(CPid) -> CPid ! {tcp_passive, sock} end). +g_handle_sock_passive(_) -> + with_client(fun(CPid) -> CPid ! {tcp_passive, sock} end, []). t_handle_sock_activate(_) -> with_connection(fun(CPid) -> CPid ! activate_socket end). @@ -313,6 +327,18 @@ with_connection(TestFun, Options) -> TrapExit orelse emqx_connection:stop(CPid), ok. +with_client(TestFun, _Options) -> + ClientId = <<"t_conn">>, + {ok, C} = emqtt:start_link([{clientid, ClientId}]), + {ok, _} = emqtt:connect(C), + timer:sleep(50), + case emqx_cm:lookup_channels(ClientId) of + [] -> ct:fail({client_not_started, ClientId}); + [ChanPid] -> + TestFun(ChanPid), + emqtt:stop(C) + end. + trap_exit(Pid, Reason) -> receive {'EXIT', Pid, Reason} -> ok; From 422c3525d83822dc1b3b0af643e8b04f72c0a3a0 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Fri, 1 Nov 2019 20:12:18 +0800 Subject: [PATCH 38/40] Move test case of checking oom to emqx_misc_SUITE --- test/emqx_misc_SUITE.erl | 8 +++++++ test/emqx_oom_SUITE.erl | 47 ---------------------------------------- 2 files changed, 8 insertions(+), 47 deletions(-) delete mode 100644 test/emqx_oom_SUITE.erl diff --git a/test/emqx_misc_SUITE.erl b/test/emqx_misc_SUITE.erl index 98bb7b072..cb7661a3e 100644 --- a/test/emqx_misc_SUITE.erl +++ b/test/emqx_misc_SUITE.erl @@ -119,6 +119,14 @@ t_index_of(_) -> end, ?assertEqual(3, emqx_misc:index_of(a, [b, c, a, e, f])). +t_check(_) -> + Policy = #{message_queue_len => 10, + max_heap_size => 1024 * 1024 * 8}, + [self() ! {msg, I} || I <- lists:seq(1, 5)], + ?assertEqual(ok, emqx_misc:check_oom(Policy)), + [self() ! {msg, I} || I <- lists:seq(1, 6)], + ?assertEqual({shutdown, message_queue_too_long}, emqx_misc:check_oom(Policy)). + drain() -> drain([]). diff --git a/test/emqx_oom_SUITE.erl b/test/emqx_oom_SUITE.erl deleted file mode 100644 index a44d32d2c..000000000 --- a/test/emqx_oom_SUITE.erl +++ /dev/null @@ -1,47 +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_oom_SUITE). - --compile(export_all). --compile(nowarn_export_all). - --include_lib("eunit/include/eunit.hrl"). - -all() -> emqx_ct:all(?MODULE). - -t_init(_) -> - Opts = #{message_queue_len => 10, - max_heap_size => 1024*1024*8 - }, - Oom = emqx_oom:init(Opts), - ?assertEqual(#{message_queue_len => 10, - max_heap_size => 1024*1024 - }, emqx_oom:info(Oom)). - -t_check(_) -> - Opts = #{message_queue_len => 10, - max_heap_size => 1024*1024*8 - }, - Oom = emqx_oom:init(Opts), - [self() ! {msg, I} || I <- lists:seq(1, 5)], - ?assertEqual(ok, emqx_oom:check(Oom)), - [self() ! {msg, I} || I <- lists:seq(1, 6)], - ?assertEqual({shutdown, message_queue_too_long}, emqx_oom:check(Oom)). - -% t_info(_) -> -% error('TODO'). - From b17a54d3922adb756221065dd797d37befff94c0 Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 20:25:29 +0800 Subject: [PATCH 39/40] Fix testcases for emqx_ws_connection --- test/emqx_ws_connection_SUITE.erl | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/test/emqx_ws_connection_SUITE.erl b/test/emqx_ws_connection_SUITE.erl index d2fee67d8..bbb789fdc 100644 --- a/test/emqx_ws_connection_SUITE.erl +++ b/test/emqx_ws_connection_SUITE.erl @@ -52,10 +52,6 @@ init_per_testcase(_TestCase, Config) -> ok = meck:expect(cowboy_req, parse_cookies, fun(_) -> undefined end), %% Meck Channel ok = meck:new(emqx_channel, [passthrough, no_history]), - ok = meck:expect(emqx_channel, recvd, - fun(_Oct, Channel) -> - {ok, Channel} - end), %% Meck Metrics ok = meck:new(emqx_metrics, [passthrough, no_history]), ok = meck:expect(emqx_metrics, inc, fun(_, _) -> ok end), @@ -83,15 +79,7 @@ t_ws_conn_info(_) -> #{socktype := ws, peername := {{127,0,0,1}, 3456}, sockname := {{127,0,0,1}, 8883}, - sockstate := idle} = SockInfo - end). - -t_ws_conn_stats(_) -> - with_ws_conn(fun(WsConn) -> - Stats = emqx_ws_connection:stats(WsConn), - lists:foreach(fun(Key) -> - 0 = proplists:get_value(Key, Stats) - end, ?STATS_KEYS) + sockstate := running} = SockInfo end). t_websocket_init(_) -> @@ -100,14 +88,13 @@ t_websocket_init(_) -> #{socktype := ws, peername := {{127,0,0,1}, 3456}, sockname := {{127,0,0,1}, 8883}, - sockstate := idle + sockstate := running } = SockInfo end). t_websocket_handle_binary(_) -> with_ws_conn(fun(WsConn) -> - ok = meck:expect(emqx_channel, recvd, fun(_Oct, Channel) -> Channel end), - {ok, WsConn} = websocket_handle({binary, [<<>>]}, WsConn) + {ok, _} = websocket_handle({binary, [<<>>]}, WsConn) end). t_websocket_handle_ping_pong(_) -> @@ -225,7 +212,7 @@ with_ws_conn(TestFun) -> with_ws_conn(TestFun, []). with_ws_conn(TestFun, Opts) -> - {ok, WsConn} = emqx_ws_connection:websocket_init( + {ok, WsConn, _} = emqx_ws_connection:websocket_init( [req, emqx_misc:merge_opts([{zone, external}], Opts)]), TestFun(WsConn). From df3d7b628b7a06a1f668bda707bb9d86bc8205ff Mon Sep 17 00:00:00 2001 From: terry-xiaoyu <506895667@qq.com> Date: Fri, 1 Nov 2019 21:36:30 +0800 Subject: [PATCH 40/40] Update emqx_ws_connection for clientinfo --- src/emqx_ws_connection.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/emqx_ws_connection.erl b/src/emqx_ws_connection.erl index c5e849c1e..c700cad19 100644 --- a/src/emqx_ws_connection.erl +++ b/src/emqx_ws_connection.erl @@ -350,14 +350,14 @@ handle_call(From, Req, State = #state{channel = Channel}) -> %% Handle Info handle_info({connack, ConnAck}, State = #state{channel = Channel}) -> - ClientId = emqx_channel:info(clientid, Channel), + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), ok = emqx_cm:register_channel(ClientId), ok = emqx_cm:set_chan_attrs(ClientId, attrs(State)), ok = emqx_cm:set_chan_stats(ClientId, stats(State)), reply(enqueue(ConnAck, State)); handle_info({enter, disconnected}, State = #state{channel = Channel}) -> - ClientId = emqx_channel:info(clientid, Channel), + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), emqx_cm:set_chan_attrs(ClientId, attrs(State)), emqx_cm:set_chan_stats(ClientId, stats(State)), reply(State); @@ -378,7 +378,7 @@ handle_timeout(TRef, keepalive, State) when is_reference(TRef) -> handle_timeout(TRef, emit_stats, State = #state{channel = Channel, stats_timer = TRef}) -> - ClientId = emqx_channel:info(clientid, Channel), + #{clientid := ClientId} = emqx_channel:info(clientinfo, Channel), (ClientId =/= undefined) andalso emqx_cm:set_chan_stats(ClientId, stats(State)), reply(State#state{stats_timer = undefined});