|
|
@@ -19,7 +19,6 @@
|
|
|
-compile(export_all).
|
|
|
-compile(nowarn_export_all).
|
|
|
|
|
|
--include_lib("proper/include/proper.hrl").
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
|
all() ->
|
|
|
@@ -30,8 +29,8 @@ t_insert(_) ->
|
|
|
true = emqx_rule_index:insert(<<"sensor/1/metric/2">>, t_insert_1, <<>>, Tab),
|
|
|
true = emqx_rule_index:insert(<<"sensor/+/#">>, t_insert_2, <<>>, Tab),
|
|
|
true = emqx_rule_index:insert(<<"sensor/#">>, t_insert_3, <<>>, Tab),
|
|
|
- ?assertEqual(<<"sensor/#">>, topic(match(<<"sensor">>, Tab))),
|
|
|
- ?assertEqual(t_insert_3, id(match(<<"sensor">>, Tab))),
|
|
|
+ ?assertEqual(<<"sensor/#">>, get_topic(match(<<"sensor">>, Tab))),
|
|
|
+ ?assertEqual(t_insert_3, get_id(match(<<"sensor">>, Tab))),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
t_match(_) ->
|
|
|
@@ -41,7 +40,7 @@ t_match(_) ->
|
|
|
true = emqx_rule_index:insert(<<"sensor/#">>, t_match_3, <<>>, Tab),
|
|
|
?assertMatch(
|
|
|
[<<"sensor/#">>, <<"sensor/+/#">>],
|
|
|
- [topic(M) || M <- matches(<<"sensor/1">>, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(<<"sensor/1">>, Tab)]
|
|
|
),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
@@ -52,7 +51,7 @@ t_match2(_) ->
|
|
|
true = emqx_rule_index:insert(<<"+/+/#">>, t_match2_3, <<>>, Tab),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<"+/#">>, <<"+/+/#">>],
|
|
|
- [topic(M) || M <- matches(<<"a/b/c">>, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(<<"a/b/c">>, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
false,
|
|
|
@@ -80,7 +79,7 @@ t_match3(_) ->
|
|
|
end,
|
|
|
?assertEqual(
|
|
|
t_match3_sys,
|
|
|
- id(match(<<"$SYS/a/b/c">>, Tab))
|
|
|
+ get_id(match(<<"$SYS/a/b/c">>, Tab))
|
|
|
),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
@@ -93,11 +92,11 @@ t_match4(_) ->
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"/#">>, <<"/+">>],
|
|
|
- [topic(M) || M <- matches(<<"/">>, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(<<"/">>, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"/#">>, <<"/+/a/b/c">>],
|
|
|
- [topic(M) || M <- matches(<<"/0/a/b/c">>, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(<<"/0/a/b/c">>, Tab)]
|
|
|
),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
@@ -115,11 +114,11 @@ t_match5(_) ->
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<T/binary, "/#">>],
|
|
|
- [topic(M) || M <- matches(T, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(T, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<T/binary, "/#">>, <<T/binary, "/+">>],
|
|
|
- [topic(M) || M <- matches(<<T/binary, "/1">>, Tab)]
|
|
|
+ [get_topic(M) || M <- matches(<<T/binary, "/1">>, Tab)]
|
|
|
),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
@@ -128,7 +127,7 @@ t_match6(_) ->
|
|
|
T = <<"a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z">>,
|
|
|
W = <<"+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/+/#">>,
|
|
|
emqx_rule_index:insert(W, ID = t_match6, <<>>, Tab),
|
|
|
- ?assertEqual(ID, id(match(T, Tab))),
|
|
|
+ ?assertEqual(ID, get_id(match(T, Tab))),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
t_match7(_) ->
|
|
|
@@ -136,173 +135,32 @@ t_match7(_) ->
|
|
|
T = <<"a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z">>,
|
|
|
W = <<"a/+/c/+/e/+/g/+/i/+/k/+/m/+/o/+/q/+/s/+/u/+/w/+/y/+/#">>,
|
|
|
emqx_rule_index:insert(W, t_match7, <<>>, Tab),
|
|
|
- ?assertEqual(W, topic(match(T, Tab))),
|
|
|
+ ?assertEqual(W, get_topic(match(T, Tab))),
|
|
|
true = ets:delete(Tab).
|
|
|
|
|
|
-t_match_unique(_) ->
|
|
|
- Tab = new(),
|
|
|
- emqx_rule_index:insert(<<"a/b/c">>, t_match_id1, <<>>, Tab),
|
|
|
- emqx_rule_index:insert(<<"a/b/+">>, t_match_id1, <<>>, Tab),
|
|
|
- emqx_rule_index:insert(<<"a/b/c/+">>, t_match_id2, <<>>, Tab),
|
|
|
- ?assertEqual(
|
|
|
- [t_match_id1, t_match_id1],
|
|
|
- [id(M) || M <- emqx_rule_index:matches(<<"a/b/c">>, Tab, [])]
|
|
|
- ),
|
|
|
- ?assertEqual(
|
|
|
- [t_match_id1],
|
|
|
- [id(M) || M <- emqx_rule_index:matches(<<"a/b/c">>, Tab, [unique])]
|
|
|
- ).
|
|
|
-
|
|
|
-t_match_ordering(_) ->
|
|
|
- Tab = new(),
|
|
|
- emqx_rule_index:insert(<<"a/b/+">>, t_match_id2, <<>>, Tab),
|
|
|
- emqx_rule_index:insert(<<"a/b/c">>, t_match_id1, <<>>, Tab),
|
|
|
- emqx_rule_index:insert(<<"a/b/#">>, t_match_id3, <<>>, Tab),
|
|
|
- Ids1 = [id(M) || M <- emqx_rule_index:matches(<<"a/b/c">>, Tab, [])],
|
|
|
- Ids2 = [id(M) || M <- emqx_rule_index:matches(<<"a/b/c">>, Tab, [unique])],
|
|
|
- ?assertEqual(Ids1, Ids2),
|
|
|
- ?assertEqual([t_match_id1, t_match_id2, t_match_id3], Ids1).
|
|
|
-
|
|
|
-t_match_wildcard_edge_cases(_) ->
|
|
|
- CommonTopics = [
|
|
|
- <<"a/b">>,
|
|
|
- <<"a/b/#">>,
|
|
|
- <<"a/b/#">>,
|
|
|
- <<"a/b/c">>,
|
|
|
- <<"a/b/+">>,
|
|
|
- <<"a/b/d">>,
|
|
|
- <<"a/+/+">>,
|
|
|
- <<"a/+/#">>
|
|
|
- ],
|
|
|
- Datasets =
|
|
|
- [
|
|
|
- %% Topics, TopicName, Results
|
|
|
- {CommonTopics, <<"a/b/c">>, [2, 3, 4, 5, 7, 8]},
|
|
|
- {CommonTopics, <<"a/b">>, [1, 2, 3, 8]},
|
|
|
- {[<<"+/b/c">>, <<"/">>], <<"a/b/c">>, [1]},
|
|
|
- {[<<"#">>, <<"/">>], <<"a">>, [1]},
|
|
|
- {[<<"/">>, <<"+">>], <<"a">>, [2]}
|
|
|
- ],
|
|
|
- F = fun({Topics, TopicName, Expected}) ->
|
|
|
- Tab = new(),
|
|
|
- _ = lists:foldl(
|
|
|
- fun(T, N) ->
|
|
|
- emqx_rule_index:insert(T, N, <<>>, Tab),
|
|
|
- N + 1
|
|
|
- end,
|
|
|
- 1,
|
|
|
- Topics
|
|
|
- ),
|
|
|
- Results = [id(M) || M <- emqx_rule_index:matches(TopicName, Tab, [unique])],
|
|
|
- case Results == Expected of
|
|
|
- true ->
|
|
|
- ets:delete(Tab);
|
|
|
- false ->
|
|
|
- ct:pal(
|
|
|
- "Base topics: ~p~n"
|
|
|
- "Topic name: ~p~n"
|
|
|
- "Index results: ~p~n"
|
|
|
- "Expected results:: ~p~n",
|
|
|
- [Topics, TopicName, Results, Expected]
|
|
|
- ),
|
|
|
- error(bad_matches)
|
|
|
- end
|
|
|
- end,
|
|
|
- lists:foreach(F, Datasets).
|
|
|
-
|
|
|
-t_prop_matches(_) ->
|
|
|
- ?assert(
|
|
|
- proper:quickcheck(
|
|
|
- topic_matches_prop(),
|
|
|
- [{max_size, 100}, {numtests, 100}]
|
|
|
- )
|
|
|
- ).
|
|
|
-
|
|
|
-topic_matches_prop() ->
|
|
|
- ?FORALL(
|
|
|
- Topics0,
|
|
|
- list(emqx_proper_types:topic()),
|
|
|
- begin
|
|
|
- Tab = new(),
|
|
|
- Topics = lists:filter(fun(Topic) -> Topic =/= <<>> end, Topics0),
|
|
|
- lists:foldl(
|
|
|
- fun(Topic, N) ->
|
|
|
- true = emqx_rule_index:insert(Topic, N, <<>>, Tab),
|
|
|
- N + 1
|
|
|
- end,
|
|
|
- 1,
|
|
|
- Topics
|
|
|
- ),
|
|
|
- lists:foreach(
|
|
|
- fun(Topic0) ->
|
|
|
- Topic = topic_filter_to_topic_name(Topic0),
|
|
|
- Ids1 = [
|
|
|
- emqx_rule_index:get_id(R)
|
|
|
- || R <- emqx_rule_index:matches(Topic, Tab, [unique])
|
|
|
- ],
|
|
|
- Ids2 = topic_matches(Topic, Topics),
|
|
|
- case Ids2 == Ids1 of
|
|
|
- true ->
|
|
|
- ok;
|
|
|
- false ->
|
|
|
- ct:pal(
|
|
|
- "Base topics: ~p~n"
|
|
|
- "Topic name: ~p~n"
|
|
|
- "Index results: ~p~n"
|
|
|
- "Topic match results:: ~p~n",
|
|
|
- [Topics, Topic, Ids1, Ids2]
|
|
|
- ),
|
|
|
- error(bad_matches)
|
|
|
- end
|
|
|
- end,
|
|
|
- Topics
|
|
|
- ),
|
|
|
- true
|
|
|
- end
|
|
|
- ).
|
|
|
-
|
|
|
-%%--------------------------------------------------------------------
|
|
|
-%% helpers
|
|
|
-
|
|
|
new() ->
|
|
|
- ets:new(?MODULE, [public, ordered_set, {read_concurrency, true}]).
|
|
|
+ ets:new(?MODULE, [public, ordered_set, {write_concurrency, true}]).
|
|
|
|
|
|
match(T, Tab) ->
|
|
|
emqx_rule_index:match(T, Tab).
|
|
|
|
|
|
matches(T, Tab) ->
|
|
|
- lists:sort(emqx_rule_index:matches(T, Tab, [])).
|
|
|
-
|
|
|
-id(Match) ->
|
|
|
- emqx_rule_index:get_id(Match).
|
|
|
+ lists:sort(emqx_rule_index:matches(T, Tab)).
|
|
|
|
|
|
-topic(Match) ->
|
|
|
- emqx_rule_index:get_topic(Match).
|
|
|
+-spec get_id(emqx_rule_index:match(ID)) -> ID.
|
|
|
+get_id([{ID}]) ->
|
|
|
+ ID;
|
|
|
+get_id([_ | Rest]) ->
|
|
|
+ get_id(Rest).
|
|
|
|
|
|
-topic_filter_to_topic_name(Topic) when is_binary(Topic) ->
|
|
|
- topic_filter_to_topic_name(emqx_topic:words(Topic));
|
|
|
-topic_filter_to_topic_name(Words) when is_list(Words) ->
|
|
|
- topic_filter_to_topic_name(Words, []).
|
|
|
+-spec get_topic(emqx_rule_index:match(_ID)) -> emqx_types:topic().
|
|
|
+get_topic(K) ->
|
|
|
+ emqx_topic:join(cut_topic(K)).
|
|
|
|
|
|
-topic_filter_to_topic_name([], Acc) ->
|
|
|
- emqx_topic:join(lists:reverse(Acc));
|
|
|
-topic_filter_to_topic_name(['#' | _Rest], Acc) ->
|
|
|
- case rand:uniform(2) of
|
|
|
- 1 -> emqx_topic:join(lists:reverse(Acc));
|
|
|
- _ -> emqx_topic:join(lists:reverse([<<"_sharp">> | Acc]))
|
|
|
- end;
|
|
|
-topic_filter_to_topic_name(['+' | Rest], Acc) ->
|
|
|
- topic_filter_to_topic_name(Rest, [<<"_plus">> | Acc]);
|
|
|
-topic_filter_to_topic_name([H | Rest], Acc) ->
|
|
|
- topic_filter_to_topic_name(Rest, [H | Acc]).
|
|
|
+cut_topic(K) ->
|
|
|
+ cut_topic(K, []).
|
|
|
|
|
|
-topic_matches(Topic, Topics0) ->
|
|
|
- Topics = lists:zip(lists:seq(1, length(Topics0)), Topics0),
|
|
|
- lists:sort(
|
|
|
- lists:filtermap(
|
|
|
- fun({Id, Topic0}) ->
|
|
|
- emqx_topic:match(Topic, Topic0) andalso {true, Id}
|
|
|
- end,
|
|
|
- Topics
|
|
|
- )
|
|
|
- ).
|
|
|
+cut_topic([{_ID}], Acc) ->
|
|
|
+ lists:reverse(Acc);
|
|
|
+cut_topic([W | Rest], Acc) ->
|
|
|
+ cut_topic(Rest, [W | Acc]).
|