|
|
@@ -25,42 +25,71 @@
|
|
|
-import(emqx_proper_types, [scaled/2]).
|
|
|
|
|
|
all() ->
|
|
|
- emqx_common_test_helpers:all(?MODULE).
|
|
|
-
|
|
|
-t_insert(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/1/metric/2">>, t_insert_1, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/+/#">>, t_insert_2, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/#">>, t_insert_3, <<>>, Tab),
|
|
|
- ?assertEqual(<<"sensor/#">>, topic(match(<<"sensor">>, Tab))),
|
|
|
- ?assertEqual(t_insert_3, id(match(<<"sensor">>, Tab))).
|
|
|
-
|
|
|
-t_match(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/1/metric/2">>, t_match_1, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/+/#">>, t_match_2, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"sensor/#">>, t_match_3, <<>>, Tab),
|
|
|
+ [
|
|
|
+ {group, ets},
|
|
|
+ {group, gb_tree}
|
|
|
+ ].
|
|
|
+
|
|
|
+groups() ->
|
|
|
+ All = emqx_common_test_helpers:all(?MODULE),
|
|
|
+ [
|
|
|
+ {ets, All},
|
|
|
+ {gb_tree, All}
|
|
|
+ ].
|
|
|
+
|
|
|
+init_per_group(ets, Config) ->
|
|
|
+ [{index_module, emqx_topic_index} | Config];
|
|
|
+init_per_group(gb_tree, Config) ->
|
|
|
+ [{index_module, emqx_topic_gbt} | Config].
|
|
|
+
|
|
|
+end_per_group(_Group, _Config) ->
|
|
|
+ ok.
|
|
|
+
|
|
|
+get_module(Config) ->
|
|
|
+ proplists:get_value(index_module, Config).
|
|
|
+
|
|
|
+t_insert(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ true = M:insert(<<"sensor/1/metric/2">>, t_insert_1, <<>>, Tab),
|
|
|
+ true = M:insert(<<"sensor/+/#">>, t_insert_2, <<>>, Tab),
|
|
|
+ true = M:insert(<<"sensor/#">>, t_insert_3, <<>>, Tab),
|
|
|
+ ?assertEqual(<<"sensor/#">>, topic(match(M, <<"sensor">>, Tab))),
|
|
|
+ ?assertEqual(t_insert_3, id(match(M, <<"sensor">>, Tab))).
|
|
|
+
|
|
|
+t_match(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ true = M:insert(<<"sensor/1/metric/2">>, t_match_1, <<>>, Tab),
|
|
|
+ true = M:insert(<<"sensor/+/#">>, t_match_2, <<>>, Tab),
|
|
|
+ true = M:insert(<<"sensor/#">>, t_match_3, <<>>, Tab),
|
|
|
?assertMatch(
|
|
|
[<<"sensor/#">>, <<"sensor/+/#">>],
|
|
|
- [topic(M) || M <- matches(<<"sensor/1">>, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, <<"sensor/1">>, Tab)]
|
|
|
).
|
|
|
|
|
|
-t_match2(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- true = emqx_topic_index:insert(<<"#">>, t_match2_1, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"+/#">>, t_match2_2, <<>>, Tab),
|
|
|
- true = emqx_topic_index:insert(<<"+/+/#">>, t_match2_3, <<>>, Tab),
|
|
|
+t_match2(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ true = M:insert(<<"#">>, t_match2_1, <<>>, Tab),
|
|
|
+ true = M:insert(<<"+/#">>, t_match2_2, <<>>, Tab),
|
|
|
+ true = M:insert(<<"+/+/#">>, t_match2_3, <<>>, Tab),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<"+/#">>, <<"+/+/#">>],
|
|
|
- [topic(M) || M <- matches(<<"a/b/c">>, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, <<"a/b/c">>, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
false,
|
|
|
- emqx_topic_index:match(<<"$SYS/broker/zenmq">>, Tab)
|
|
|
+ M:match(<<"$SYS/broker/zenmq">>, Tab)
|
|
|
+ ),
|
|
|
+ ?assertEqual(
|
|
|
+ [],
|
|
|
+ matches(M, <<"$SYS/broker/zenmq">>, Tab)
|
|
|
).
|
|
|
|
|
|
-t_match3(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+t_match3(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
Records = [
|
|
|
{<<"d/#">>, t_match3_1},
|
|
|
{<<"a/b/+">>, t_match3_2},
|
|
|
@@ -69,37 +98,39 @@ t_match3(_) ->
|
|
|
{<<"$SYS/#">>, t_match3_sys}
|
|
|
],
|
|
|
lists:foreach(
|
|
|
- fun({Topic, ID}) -> emqx_topic_index:insert(Topic, ID, <<>>, Tab) end,
|
|
|
+ fun({Topic, ID}) -> M:insert(Topic, ID, <<>>, Tab) end,
|
|
|
Records
|
|
|
),
|
|
|
- Matched = matches(<<"a/b/c">>, Tab),
|
|
|
+ Matched = matches(M, <<"a/b/c">>, Tab),
|
|
|
case length(Matched) of
|
|
|
3 -> ok;
|
|
|
_ -> error({unexpected, Matched})
|
|
|
end,
|
|
|
?assertEqual(
|
|
|
t_match3_sys,
|
|
|
- id(match(<<"$SYS/a/b/c">>, Tab))
|
|
|
+ id(match(M, <<"$SYS/a/b/c">>, Tab))
|
|
|
).
|
|
|
|
|
|
-t_match4(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+t_match4(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
Records = [{<<"/#">>, t_match4_1}, {<<"/+">>, t_match4_2}, {<<"/+/a/b/c">>, t_match4_3}],
|
|
|
lists:foreach(
|
|
|
- fun({Topic, ID}) -> emqx_topic_index:insert(Topic, ID, <<>>, Tab) end,
|
|
|
+ fun({Topic, ID}) -> M:insert(Topic, ID, <<>>, Tab) end,
|
|
|
Records
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"/#">>, <<"/+">>],
|
|
|
- [topic(M) || M <- matches(<<"/">>, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, <<"/">>, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"/#">>, <<"/+/a/b/c">>],
|
|
|
- [topic(M) || M <- matches(<<"/0/a/b/c">>, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, <<"/0/a/b/c">>, Tab)]
|
|
|
).
|
|
|
|
|
|
-t_match5(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+t_match5(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
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">>,
|
|
|
Records = [
|
|
|
{<<"#">>, t_match5_1},
|
|
|
@@ -107,58 +138,89 @@ t_match5(_) ->
|
|
|
{<<T/binary, "/+">>, t_match5_3}
|
|
|
],
|
|
|
lists:foreach(
|
|
|
- fun({Topic, ID}) -> emqx_topic_index:insert(Topic, ID, <<>>, Tab) end,
|
|
|
+ fun({Topic, ID}) -> M:insert(Topic, ID, <<>>, Tab) end,
|
|
|
Records
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<T/binary, "/#">>],
|
|
|
- [topic(M) || M <- matches(T, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, T, Tab)]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[<<"#">>, <<T/binary, "/#">>, <<T/binary, "/+">>],
|
|
|
- [topic(M) || M <- matches(<<T/binary, "/1">>, Tab)]
|
|
|
+ [topic(X) || X <- matches(M, <<T/binary, "/1">>, Tab)]
|
|
|
).
|
|
|
|
|
|
-t_match6(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+t_match6(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
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_topic_index:insert(W, ID = t_match6, <<>>, Tab),
|
|
|
- ?assertEqual(ID, id(match(T, Tab))).
|
|
|
+ M:insert(W, ID = t_match6, <<>>, Tab),
|
|
|
+ ?assertEqual(ID, id(match(M, T, Tab))).
|
|
|
|
|
|
-t_match7(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+t_match7(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
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_topic_index:insert(W, t_match7, <<>>, Tab),
|
|
|
- ?assertEqual(W, topic(match(T, Tab))).
|
|
|
-
|
|
|
-t_match_fast_forward(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- emqx_topic_index:insert(<<"a/b/1/2/3/4/5/6/7/8/9/#">>, id1, <<>>, Tab),
|
|
|
- emqx_topic_index:insert(<<"z/y/x/+/+">>, id2, <<>>, Tab),
|
|
|
- emqx_topic_index:insert(<<"a/b/c/+">>, id3, <<>>, Tab),
|
|
|
+ M:insert(W, t_match7, <<>>, Tab),
|
|
|
+ ?assertEqual(W, topic(match(M, T, Tab))).
|
|
|
+
|
|
|
+t_match8(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ Filters = [<<"+">>, <<"dev/global/sensor">>, <<"dev/+/sensor/#">>],
|
|
|
+ IDs = [1, 2, 3],
|
|
|
+ Keys = [{F, ID} || F <- Filters, ID <- IDs],
|
|
|
+ lists:foreach(
|
|
|
+ fun({F, ID}) ->
|
|
|
+ M:insert(F, ID, <<>>, Tab)
|
|
|
+ end,
|
|
|
+ Keys
|
|
|
+ ),
|
|
|
+ Topic = <<"dev/global/sensor">>,
|
|
|
+ Matches = lists:sort(matches(M, Topic, Tab)),
|
|
|
+ ?assertEqual(
|
|
|
+ [
|
|
|
+ <<"dev/+/sensor/#">>,
|
|
|
+ <<"dev/+/sensor/#">>,
|
|
|
+ <<"dev/+/sensor/#">>,
|
|
|
+ <<"dev/global/sensor">>,
|
|
|
+ <<"dev/global/sensor">>,
|
|
|
+ <<"dev/global/sensor">>
|
|
|
+ ],
|
|
|
+ [emqx_topic_index:get_topic(Match) || Match <- Matches]
|
|
|
+ ).
|
|
|
+
|
|
|
+t_match_fast_forward(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ M:insert(<<"a/b/1/2/3/4/5/6/7/8/9/#">>, id1, <<>>, Tab),
|
|
|
+ M:insert(<<"z/y/x/+/+">>, id2, <<>>, Tab),
|
|
|
+ M:insert(<<"a/b/c/+">>, id3, <<>>, Tab),
|
|
|
% dbg:tracer(),
|
|
|
% dbg:p(all, c),
|
|
|
% dbg:tpl({ets, next, '_'}, x),
|
|
|
- ?assertEqual(id1, id(match(<<"a/b/1/2/3/4/5/6/7/8/9/0">>, Tab))),
|
|
|
- ?assertEqual([id1], [id(M) || M <- matches(<<"a/b/1/2/3/4/5/6/7/8/9/0">>, Tab)]).
|
|
|
-
|
|
|
-t_match_unique(_) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- emqx_topic_index:insert(<<"a/b/c">>, t_match_id1, <<>>, Tab),
|
|
|
- emqx_topic_index:insert(<<"a/b/+">>, t_match_id1, <<>>, Tab),
|
|
|
- emqx_topic_index:insert(<<"a/b/c/+">>, t_match_id2, <<>>, Tab),
|
|
|
+ ?assertEqual(id1, id(match(M, <<"a/b/1/2/3/4/5/6/7/8/9/0">>, Tab))),
|
|
|
+ ?assertEqual([id1], [id(X) || X <- matches(M, <<"a/b/1/2/3/4/5/6/7/8/9/0">>, Tab)]).
|
|
|
+
|
|
|
+t_match_unique(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ M:insert(<<"a/b/c">>, t_match_id1, <<>>, Tab),
|
|
|
+ M:insert(<<"a/b/+">>, t_match_id1, <<>>, Tab),
|
|
|
+ M:insert(<<"a/b/c/+">>, t_match_id2, <<>>, Tab),
|
|
|
?assertEqual(
|
|
|
[t_match_id1, t_match_id1],
|
|
|
- [id(M) || M <- emqx_topic_index:matches(<<"a/b/c">>, Tab, [])]
|
|
|
+ [id(X) || X <- matches(M, <<"a/b/c">>, Tab, [])]
|
|
|
),
|
|
|
?assertEqual(
|
|
|
[t_match_id1],
|
|
|
- [id(M) || M <- emqx_topic_index:matches(<<"a/b/c">>, Tab, [unique])]
|
|
|
+ [id(X) || X <- matches(M, <<"a/b/c">>, Tab, [unique])]
|
|
|
).
|
|
|
|
|
|
-t_match_wildcard_edge_cases(_) ->
|
|
|
+t_match_wildcard_edge_cases(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
CommonTopics = [
|
|
|
<<"a/b">>,
|
|
|
<<"a/b/#">>,
|
|
|
@@ -179,32 +241,46 @@ t_match_wildcard_edge_cases(_) ->
|
|
|
{[<<"/">>, <<"+">>], <<"a">>, [2]}
|
|
|
],
|
|
|
F = fun({Topics, TopicName, Expected}) ->
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
- _ = [emqx_topic_index:insert(T, N, <<>>, Tab) || {N, T} <- lists:enumerate(Topics)],
|
|
|
+ Tab = M:new(),
|
|
|
+ _ = [M:insert(T, N, <<>>, Tab) || {N, T} <- lists:enumerate(Topics)],
|
|
|
?assertEqual(
|
|
|
lists:last(Expected),
|
|
|
- id(emqx_topic_index:match(TopicName, Tab)),
|
|
|
+ id(M:match(TopicName, Tab)),
|
|
|
#{"Base topics" => Topics, "Topic name" => TopicName}
|
|
|
),
|
|
|
?assertEqual(
|
|
|
Expected,
|
|
|
- [id(M) || M <- emqx_topic_index:matches(TopicName, Tab, [unique])],
|
|
|
+ [id(X) || X <- matches(M, TopicName, Tab, [unique])],
|
|
|
#{"Base topics" => Topics, "Topic name" => TopicName}
|
|
|
)
|
|
|
end,
|
|
|
lists:foreach(F, Datasets).
|
|
|
|
|
|
-t_prop_matches(_) ->
|
|
|
+t_prop_edgecase(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
+ Tab = M:new(),
|
|
|
+ Topic = <<"01/01">>,
|
|
|
+ Filters = [
|
|
|
+ {1, <<>>},
|
|
|
+ {2, <<"+/01">>},
|
|
|
+ {3, <<>>},
|
|
|
+ {4, <<"+/+/01">>}
|
|
|
+ ],
|
|
|
+ _ = [M:insert(F, N, <<>>, Tab) || {N, F} <- Filters],
|
|
|
+ ?assertMatch([2], [id(X) || X <- matches(M, Topic, Tab, [unique])]).
|
|
|
+
|
|
|
+t_prop_matches(Config) ->
|
|
|
+ M = get_module(Config),
|
|
|
?assert(
|
|
|
proper:quickcheck(
|
|
|
- topic_matches_prop(),
|
|
|
+ topic_matches_prop(M),
|
|
|
[{max_size, 100}, {numtests, 100}]
|
|
|
)
|
|
|
),
|
|
|
Statistics = [{C, account(C)} || C <- [filters, topics, matches, maxhits]],
|
|
|
ct:pal("Statistics: ~p", [maps:from_list(Statistics)]).
|
|
|
|
|
|
-topic_matches_prop() ->
|
|
|
+topic_matches_prop(M) ->
|
|
|
?FORALL(
|
|
|
% Generate a longer list of topics and a shorter list of topic filter patterns.
|
|
|
#{
|
|
|
@@ -219,12 +295,12 @@ topic_matches_prop() ->
|
|
|
patterns => list(topic_filter_pattern_t())
|
|
|
}),
|
|
|
begin
|
|
|
- Tab = emqx_topic_index:new(),
|
|
|
+ Tab = M:new(),
|
|
|
Topics = [emqx_topic:join(T) || T <- TTopics],
|
|
|
% Produce topic filters from generated topics and patterns.
|
|
|
% Number of filters is equal to the number of patterns, most of the time.
|
|
|
Filters = lists:enumerate(mk_filters(Pats, TTopics)),
|
|
|
- _ = [emqx_topic_index:insert(F, N, <<>>, Tab) || {N, F} <- Filters],
|
|
|
+ _ = [M:insert(F, N, <<>>, Tab) || {N, F} <- Filters],
|
|
|
% Gather some basic statistics
|
|
|
_ = account(filters, length(Filters)),
|
|
|
_ = account(topics, NTopics = length(Topics)),
|
|
|
@@ -233,7 +309,7 @@ topic_matches_prop() ->
|
|
|
% matching it against the list of filters one by one.
|
|
|
lists:all(
|
|
|
fun(Topic) ->
|
|
|
- Ids1 = [id(M) || M <- emqx_topic_index:matches(Topic, Tab, [unique])],
|
|
|
+ Ids1 = [id(X) || X <- matches(M, Topic, Tab, [unique])],
|
|
|
Ids2 = lists:filtermap(
|
|
|
fun({N, F}) ->
|
|
|
case emqx_topic:match(Topic, F) of
|
|
|
@@ -252,8 +328,9 @@ topic_matches_prop() ->
|
|
|
ct:pal(
|
|
|
"Topic name: ~p~n"
|
|
|
"Index results: ~p~n"
|
|
|
- "Topic match results:: ~p~n",
|
|
|
- [Topic, Ids1, Ids2]
|
|
|
+ "Topic match results: ~p~n"
|
|
|
+ "Filters: ~p~n",
|
|
|
+ [Topic, Ids1, Ids2, Filters]
|
|
|
),
|
|
|
false
|
|
|
end
|
|
|
@@ -276,17 +353,20 @@ account(Counter) ->
|
|
|
|
|
|
%%
|
|
|
|
|
|
-match(T, Tab) ->
|
|
|
- emqx_topic_index:match(T, Tab).
|
|
|
+match(M, T, Tab) ->
|
|
|
+ M:match(T, Tab).
|
|
|
+
|
|
|
+matches(M, T, Tab) ->
|
|
|
+ lists:sort(M:matches(T, Tab, [])).
|
|
|
|
|
|
-matches(T, Tab) ->
|
|
|
- lists:sort(emqx_topic_index:matches(T, Tab, [])).
|
|
|
+matches(M, T, Tab, Opts) ->
|
|
|
+ M:matches(T, Tab, Opts).
|
|
|
|
|
|
id(Match) ->
|
|
|
- emqx_topic_index:get_id(Match).
|
|
|
+ emqx_trie_search:get_id(Match).
|
|
|
|
|
|
topic(Match) ->
|
|
|
- emqx_topic_index:get_topic(Match).
|
|
|
+ emqx_trie_search:get_topic(Match).
|
|
|
|
|
|
%%
|
|
|
|