emqx/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl

826 lines
33 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2021 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_rule_funcs_SUITE).
-compile(export_all).
-compile(nowarn_export_all).
-include_lib("proper/include/proper.hrl").
-include_lib("eunit/include/eunit.hrl").
-include_lib("common_test/include/ct.hrl").
-import(emqx_rule_events, [eventmsg_publish/1]).
-define(PROPTEST(F), ?assert(proper:quickcheck(F()))).
%%-define(PROPTEST(F), ?assert(proper:quickcheck(F(), [{on_output, fun ct:print/2}]))).
%%------------------------------------------------------------------------------
%% Test cases for IoT Funcs
%%------------------------------------------------------------------------------
t_msgid(_) ->
Msg = message(),
?assertEqual(undefined, apply_func(msgid, [], #{})),
?assertEqual(emqx_guid:to_hexstr(emqx_message:id(Msg)), apply_func(msgid, [], eventmsg_publish(Msg))).
t_qos(_) ->
?assertEqual(undefined, apply_func(qos, [], #{})),
?assertEqual(1, apply_func(qos, [], message())).
t_flags(_) ->
?assertEqual(#{dup => false}, apply_func(flags, [], message())).
t_flag(_) ->
Msg = message(),
Msg1 = emqx_message:set_flag(retain, Msg),
?assertNot(apply_func(flag, [dup], Msg)),
?assert(apply_func(flag, [retain], Msg1)).
t_topic(_) ->
Msg = message(),
?assertEqual(<<"topic/#">>, apply_func(topic, [], Msg)),
?assertEqual(<<"topic">>, apply_func(topic, [1], Msg)).
t_clientid(_) ->
Msg = message(),
?assertEqual(undefined, apply_func(clientid, [], #{})),
?assertEqual(<<"clientid">>, apply_func(clientid, [], Msg)).
t_clientip(_) ->
Msg = emqx_message:set_header(peerhost, {127,0,0,1}, message()),
?assertEqual(undefined, apply_func(clientip, [], #{})),
?assertEqual(<<"127.0.0.1">>, apply_func(clientip, [], eventmsg_publish(Msg))).
t_peerhost(_) ->
Msg = emqx_message:set_header(peerhost, {127,0,0,1}, message()),
?assertEqual(undefined, apply_func(peerhost, [], #{})),
?assertEqual(<<"127.0.0.1">>, apply_func(peerhost, [], eventmsg_publish(Msg))).
t_username(_) ->
Msg = emqx_message:set_header(username, <<"feng">>, message()),
?assertEqual(<<"feng">>, apply_func(username, [], eventmsg_publish(Msg))).
t_payload(_) ->
Input = emqx_message:to_map(message()),
NestedMap = #{a => #{b => #{c => c}}},
?assertEqual(<<"hello">>, apply_func(payload, [], Input#{payload => <<"hello">>})),
?assertEqual(c, apply_func(payload, [<<"a.b.c">>], Input#{payload => NestedMap})).
%%------------------------------------------------------------------------------
%% Data Type Convertion Funcs
%%------------------------------------------------------------------------------
t_str(_) ->
?assertEqual(<<"abc">>, emqx_rule_funcs:str("abc")),
?assertEqual(<<"abc">>, emqx_rule_funcs:str(abc)),
?assertEqual(<<"{\"a\":1}">>, emqx_rule_funcs:str(#{a => 1})),
?assertEqual(<<"[{\"a\":1},{\"b\":1}]">>, emqx_rule_funcs:str([#{a => 1},#{b => 1}])),
?assertEqual(<<"1">>, emqx_rule_funcs:str(1)),
?assertEqual(<<"2.0">>, emqx_rule_funcs:str(2.0)),
?assertEqual(<<"true">>, emqx_rule_funcs:str(true)),
?assertError(_, emqx_rule_funcs:str({a, v})),
?assertEqual(<<"abc">>, emqx_rule_funcs:str_utf8("abc")),
?assertEqual(<<"abc 你好"/utf8>>, emqx_rule_funcs:str_utf8("abc 你好")),
?assertEqual(<<"abc 你好"/utf8>>, emqx_rule_funcs:str_utf8(<<"abc 你好"/utf8>>)),
?assertEqual(<<"abc">>, emqx_rule_funcs:str_utf8(abc)),
?assertEqual(<<"{\"a\":\"abc 你好\"}"/utf8>>, emqx_rule_funcs:str_utf8(#{a => <<"abc 你好"/utf8>>})),
?assertEqual(<<"1">>, emqx_rule_funcs:str_utf8(1)),
?assertEqual(<<"2.0">>, emqx_rule_funcs:str_utf8(2.0)),
?assertEqual(<<"true">>, emqx_rule_funcs:str_utf8(true)),
?assertError(_, emqx_rule_funcs:str_utf8({a, v})).
t_int(_) ->
?assertEqual(1, emqx_rule_funcs:int("1")),
?assertEqual(1, emqx_rule_funcs:int(<<"1.0">>)),
?assertEqual(1, emqx_rule_funcs:int(1)),
?assertEqual(1, emqx_rule_funcs:int(1.9)),
?assertEqual(1, emqx_rule_funcs:int(1.0001)),
?assertEqual(1, emqx_rule_funcs:int(true)),
?assertEqual(0, emqx_rule_funcs:int(false)),
?assertError({invalid_number, {a, v}}, emqx_rule_funcs:int({a, v})),
?assertError(_, emqx_rule_funcs:int("a")).
t_float(_) ->
?assertEqual(1.0, emqx_rule_funcs:float("1.0")),
?assertEqual(1.0, emqx_rule_funcs:float(<<"1.0">>)),
?assertEqual(1.0, emqx_rule_funcs:float(1)),
?assertEqual(1.0, emqx_rule_funcs:float(1.0)),
?assertEqual(1.9, emqx_rule_funcs:float(1.9)),
?assertEqual(1.0001, emqx_rule_funcs:float(1.0001)),
?assertEqual(1.0000000001, emqx_rule_funcs:float(1.0000000001)),
?assertError({invalid_number, {a, v}}, emqx_rule_funcs:float({a, v})),
?assertError(_, emqx_rule_funcs:float("a")).
t_map(_) ->
?assertEqual(#{ver => <<"1.0">>, name => "emqx"}, emqx_rule_funcs:map([{ver, <<"1.0">>}, {name, "emqx"}])),
?assertEqual(#{<<"a">> => 1}, emqx_rule_funcs:map(<<"{\"a\":1}">>)),
?assertError(_, emqx_rule_funcs:map(<<"a">>)),
?assertError(_, emqx_rule_funcs:map("a")),
?assertError(_, emqx_rule_funcs:map(1.0)).
t_bool(_) ->
?assertEqual(true, emqx_rule_funcs:bool(1)),
?assertEqual(true, emqx_rule_funcs:bool(1.0)),
?assertEqual(false, emqx_rule_funcs:bool(0)),
?assertEqual(false, emqx_rule_funcs:bool(0.0)),
?assertEqual(true, emqx_rule_funcs:bool(true)),
?assertEqual(true, emqx_rule_funcs:bool(<<"true">>)),
?assertEqual(false, emqx_rule_funcs:bool(false)),
?assertEqual(false, emqx_rule_funcs:bool(<<"false">>)),
?assertError({invalid_boolean, _}, emqx_rule_funcs:bool(3)).
t_proc_dict_put_get_del(_) ->
?assertEqual(undefined, emqx_rule_funcs:proc_dict_get(<<"abc">>)),
emqx_rule_funcs:proc_dict_put(<<"abc">>, 1),
?assertEqual(1, emqx_rule_funcs:proc_dict_get(<<"abc">>)),
emqx_rule_funcs:proc_dict_del(<<"abc">>),
?assertEqual(undefined, emqx_rule_funcs:proc_dict_get(<<"abc">>)).
t_term_encode(_) ->
TestData = [<<"abc">>, #{a => 1}, #{<<"3">> => [1,2,4]}],
lists:foreach(fun(Data) ->
?assertEqual(Data,
emqx_rule_funcs:term_decode(
emqx_rule_funcs:term_encode(Data)))
end, TestData).
t_hexstr2bin(_) ->
?assertEqual(<<1,2>>, emqx_rule_funcs:hexstr2bin(<<"0102">>)),
?assertEqual(<<17,33>>, emqx_rule_funcs:hexstr2bin(<<"1121">>)).
t_bin2hexstr(_) ->
?assertEqual(<<"0102">>, emqx_rule_funcs:bin2hexstr(<<1,2>>)),
?assertEqual(<<"1121">>, emqx_rule_funcs:bin2hexstr(<<17,33>>)).
t_hex_convert(_) ->
?PROPTEST(hex_convert).
hex_convert() ->
?FORALL(L, list(range(0, 255)),
begin
AbitraryBin = list_to_binary(L),
AbitraryBin == emqx_rule_funcs:hexstr2bin(
emqx_rule_funcs:bin2hexstr(AbitraryBin))
end).
t_is_null(_) ->
?assertEqual(true, emqx_rule_funcs:is_null(undefined)),
?assertEqual(false, emqx_rule_funcs:is_null(a)),
?assertEqual(false, emqx_rule_funcs:is_null(<<>>)),
?assertEqual(false, emqx_rule_funcs:is_null(<<"a">>)).
t_is_not_null(_) ->
[?assertEqual(emqx_rule_funcs:is_not_null(T), not emqx_rule_funcs:is_null(T))
|| T <- [undefined, a, <<"a">>, <<>>]].
t_is_str(_) ->
[?assertEqual(true, emqx_rule_funcs:is_str(T))
|| T <- [<<"a">>, <<>>, <<"abc">>]],
[?assertEqual(false, emqx_rule_funcs:is_str(T))
|| T <- ["a", a, 1]].
t_is_bool(_) ->
[?assertEqual(true, emqx_rule_funcs:is_bool(T))
|| T <- [true, false]],
[?assertEqual(false, emqx_rule_funcs:is_bool(T))
|| T <- ["a", <<>>, a, 2]].
t_is_int(_) ->
[?assertEqual(true, emqx_rule_funcs:is_int(T))
|| T <- [1, 2, -1]],
[?assertEqual(false, emqx_rule_funcs:is_int(T))
|| T <- [1.1, "a", a]].
t_is_float(_) ->
[?assertEqual(true, emqx_rule_funcs:is_float(T))
|| T <- [1.1, 2.0, -1.2]],
[?assertEqual(false, emqx_rule_funcs:is_float(T))
|| T <- [1, "a", a, <<>>]].
t_is_num(_) ->
[?assertEqual(true, emqx_rule_funcs:is_num(T))
|| T <- [1.1, 2.0, -1.2, 1]],
[?assertEqual(false, emqx_rule_funcs:is_num(T))
|| T <- ["a", a, <<>>]].
t_is_map(_) ->
[?assertEqual(true, emqx_rule_funcs:is_map(T))
|| T <- [#{}, #{a =>1}]],
[?assertEqual(false, emqx_rule_funcs:is_map(T))
|| T <- ["a", a, <<>>]].
t_is_array(_) ->
[?assertEqual(true, emqx_rule_funcs:is_array(T))
|| T <- [[], [1,2]]],
[?assertEqual(false, emqx_rule_funcs:is_array(T))
|| T <- [<<>>, a]].
%%------------------------------------------------------------------------------
%% Test cases for arith op
%%------------------------------------------------------------------------------
t_arith_op(_) ->
?PROPTEST(prop_arith_op).
prop_arith_op() ->
?FORALL({X, Y}, {number(), number()},
begin
(X + Y) == apply_func('+', [X, Y]) andalso
(X - Y) == apply_func('-', [X, Y]) andalso
(X * Y) == apply_func('*', [X, Y]) andalso
(if Y =/= 0 ->
(X / Y) == apply_func('/', [X, Y]);
true -> true
end) andalso
(case is_integer(X)
andalso is_pos_integer(Y) of
true ->
(X rem Y) == apply_func('mod', [X, Y]);
false -> true
end)
end).
is_pos_integer(X) ->
is_integer(X) andalso X > 0.
%%------------------------------------------------------------------------------
%% Test cases for math fun
%%------------------------------------------------------------------------------
t_math_fun(_) ->
?PROPTEST(prop_math_fun).
prop_math_fun() ->
Excluded = [module_info, atanh, asin, acos],
MathFuns = [{F, A} || {F, A} <- math:module_info(exports),
not lists:member(F, Excluded),
erlang:function_exported(emqx_rule_funcs, F, A)],
?FORALL({X, Y}, {pos_integer(), pos_integer()},
begin
lists:foldl(fun({F, 1}, True) ->
True andalso comp_with_math(F, X);
({F = fmod, 2}, True) ->
True andalso (if Y =/= 0 ->
comp_with_math(F, X, Y);
true -> true
end);
({F, 2}, True) ->
True andalso comp_with_math(F, X, Y)
end, true, MathFuns)
end).
comp_with_math(Fun, X)
when Fun =:= exp;
Fun =:= sinh;
Fun =:= cosh ->
if X < 710 -> math:Fun(X) == apply_func(Fun, [X]);
true -> true
end;
comp_with_math(F, X) ->
math:F(X) == apply_func(F, [X]).
comp_with_math(F, X, Y) ->
math:F(X, Y) == apply_func(F, [X, Y]).
%%------------------------------------------------------------------------------
%% Test cases for bits op
%%------------------------------------------------------------------------------
t_bits_op(_) ->
?PROPTEST(prop_bits_op).
prop_bits_op() ->
?FORALL({X, Y}, {integer(), integer()},
begin
(bnot X) == apply_func(bitnot, [X]) andalso
(X band Y) == apply_func(bitand, [X, Y]) andalso
(X bor Y) == apply_func(bitor, [X, Y]) andalso
(X bxor Y) == apply_func(bitxor, [X, Y]) andalso
(X bsl Y) == apply_func(bitsl, [X, Y]) andalso
(X bsr Y) == apply_func(bitsr, [X, Y])
end).
%%------------------------------------------------------------------------------
%% Test cases for string
%%------------------------------------------------------------------------------
t_lower_upper(_) ->
?assertEqual(<<"ABC4">>, apply_func(upper, [<<"abc4">>])),
?assertEqual(<<"0abc">>, apply_func(lower, [<<"0ABC">>])).
t_reverse(_) ->
?assertEqual(<<"dcba">>, apply_func(reverse, [<<"abcd">>])),
?assertEqual(<<"4321">>, apply_func(reverse, [<<"1234">>])).
t_strlen(_) ->
?assertEqual(4, apply_func(strlen, [<<"abcd">>])),
?assertEqual(2, apply_func(strlen, [<<"你好">>])).
t_substr(_) ->
?assertEqual(<<"">>, apply_func(substr, [<<"">>, 1])),
?assertEqual(<<"bc">>, apply_func(substr, [<<"abc">>, 1])),
?assertEqual(<<"bc">>, apply_func(substr, [<<"abcd">>, 1, 2])).
t_trim(_) ->
?assertEqual(<<>>, apply_func(trim, [<<>>])),
?assertEqual(<<>>, apply_func(ltrim, [<<>>])),
?assertEqual(<<>>, apply_func(rtrim, [<<>>])),
?assertEqual(<<"abc">>, apply_func(trim, [<<" abc ">>])),
?assertEqual(<<"abc ">>, apply_func(ltrim, [<<" abc ">>])),
?assertEqual(<<" abc">>, apply_func(rtrim, [<<" abc">>])).
t_split_all(_) ->
?assertEqual([], apply_func(split, [<<>>, <<"/">>])),
?assertEqual([], apply_func(split, [<<"/">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(split, [<<"/a/b//c/">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(split, [<<"a,b,c">>, <<",">>])),
?assertEqual([<<"a">>,<<" b ">>,<<"c">>], apply_func(split, [<<"a, b ,c">>, <<",">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c\n">>], apply_func(split, [<<"a,b,c\n">>, <<",">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c\r\n">>], apply_func(split, [<<"a,b,c\r\n">>, <<",">>])).
t_split_notrim_all(_) ->
?assertEqual([<<>>], apply_func(split, [<<>>, <<"/">>, <<"notrim">>])),
?assertEqual([<<>>,<<>>], apply_func(split, [<<"/">>, <<"/">>, <<"notrim">>])),
?assertEqual([<<>>, <<"a">>,<<"b">>,<<"c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>, <<"notrim">>])),
?assertEqual([<<>>, <<"a">>,<<"b">>, <<>>, <<"c">>, <<>>], apply_func(split, [<<"/a/b//c/">>, <<"/">>, <<"notrim">>])),
?assertEqual([<<>>, <<"a">>,<<"b">>,<<"c\n">>], apply_func(split, [<<",a,b,c\n">>, <<",">>, <<"notrim">>])),
?assertEqual([<<"a">>,<<" b">>,<<"c\r\n">>], apply_func(split, [<<"a, b,c\r\n">>, <<",">>, <<"notrim">>])),
?assertEqual([<<"哈哈"/utf8>>,<<" 你好"/utf8>>,<<" 是的\r\n"/utf8>>], apply_func(split, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<",">>, <<"notrim">>])).
t_split_leading(_) ->
?assertEqual([], apply_func(split, [<<>>, <<"/">>, <<"leading">>])),
?assertEqual([], apply_func(split, [<<"/">>, <<"/">>, <<"leading">>])),
?assertEqual([<<"a/b/c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>, <<"leading">>])),
?assertEqual([<<"a">>,<<"b//c/">>], apply_func(split, [<<"a/b//c/">>, <<"/">>, <<"leading">>])),
?assertEqual([<<"a">>,<<"b,c\n">>], apply_func(split, [<<"a,b,c\n">>, <<",">>, <<"leading">>])),
?assertEqual([<<"a b">>,<<"c\r\n">>], apply_func(split, [<<"a b,c\r\n">>, <<",">>, <<"leading">>])),
?assertEqual([<<"哈哈"/utf8>>,<<" 你好, 是的\r\n"/utf8>>], apply_func(split, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<",">>, <<"leading">>])).
t_split_leading_notrim(_) ->
?assertEqual([<<>>], apply_func(split, [<<>>, <<"/">>, <<"leading_notrim">>])),
?assertEqual([<<>>,<<>>], apply_func(split, [<<"/">>, <<"/">>, <<"leading_notrim">>])),
?assertEqual([<<>>, <<"a/b/c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>, <<"leading_notrim">>])),
?assertEqual([<<"a">>,<<"b//c/">>], apply_func(split, [<<"a/b//c/">>, <<"/">>, <<"leading_notrim">>])),
?assertEqual([<<"a">>,<<"b,c\n">>], apply_func(split, [<<"a,b,c\n">>, <<",">>, <<"leading_notrim">>])),
?assertEqual([<<"a b">>,<<"c\r\n">>], apply_func(split, [<<"a b,c\r\n">>, <<",">>, <<"leading_notrim">>])),
?assertEqual([<<"哈哈"/utf8>>,<<" 你好, 是的\r\n"/utf8>>], apply_func(split, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<",">>, <<"leading_notrim">>])).
t_split_trailing(_) ->
?assertEqual([], apply_func(split, [<<>>, <<"/">>, <<"trailing">>])),
?assertEqual([], apply_func(split, [<<"/">>, <<"/">>, <<"trailing">>])),
?assertEqual([<<"/a/b">>, <<"c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>, <<"trailing">>])),
?assertEqual([<<"a/b//c">>], apply_func(split, [<<"a/b//c/">>, <<"/">>, <<"trailing">>])),
?assertEqual([<<"a,b">>,<<"c\n">>], apply_func(split, [<<"a,b,c\n">>, <<",">>, <<"trailing">>])),
?assertEqual([<<"a b">>,<<"c\r\n">>], apply_func(split, [<<"a b,c\r\n">>, <<",">>, <<"trailing">>])),
?assertEqual([<<"哈哈, 你好"/utf8>>,<<" 是的\r\n"/utf8>>], apply_func(split, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<",">>, <<"trailing">>])).
t_split_trailing_notrim(_) ->
?assertEqual([<<>>], apply_func(split, [<<>>, <<"/">>, <<"trailing_notrim">>])),
?assertEqual([<<>>, <<>>], apply_func(split, [<<"/">>, <<"/">>, <<"trailing_notrim">>])),
?assertEqual([<<"/a/b">>, <<"c">>], apply_func(split, [<<"/a/b/c">>, <<"/">>, <<"trailing_notrim">>])),
?assertEqual([<<"a/b//c">>, <<>>], apply_func(split, [<<"a/b//c/">>, <<"/">>, <<"trailing_notrim">>])),
?assertEqual([<<"a,b">>,<<"c\n">>], apply_func(split, [<<"a,b,c\n">>, <<",">>, <<"trailing_notrim">>])),
?assertEqual([<<"a b">>,<<"c\r\n">>], apply_func(split, [<<"a b,c\r\n">>, <<",">>, <<"trailing_notrim">>])),
?assertEqual([<<"哈哈, 你好"/utf8>>,<<" 是的\r\n"/utf8>>], apply_func(split, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<",">>, <<"trailing_notrim">>])).
t_tokens(_) ->
?assertEqual([], apply_func(tokens, [<<>>, <<"/">>])),
?assertEqual([], apply_func(tokens, [<<"/">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<"/a/b/c">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<"/a/b//c/">>, <<"/">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<" /a/ b /c">>, <<" /">>])),
?assertEqual([<<"a">>,<<"\nb">>,<<"c\n">>], apply_func(tokens, [<<"a ,\nb,c\n">>, <<", ">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c\r\n">>], apply_func(tokens, [<<"a ,b,c\r\n">>, <<", ">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<"a,b, c\n">>, <<", ">>, <<"nocrlf">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<"a,b,c\r\n">>, <<",">>, <<"nocrlf">>])),
?assertEqual([<<"a">>,<<"b">>,<<"c">>], apply_func(tokens, [<<"a,b\r\n,c\n">>, <<",">>, <<"nocrlf">>])),
?assertEqual([], apply_func(tokens, [<<"\r\n">>, <<",">>, <<"nocrlf">>])),
?assertEqual([], apply_func(tokens, [<<"\r\n">>, <<",">>, <<"nocrlf">>])),
?assertEqual([<<"哈哈"/utf8>>,<<"你好"/utf8>>,<<"是的"/utf8>>], apply_func(tokens, [<<"哈哈, 你好, 是的\r\n"/utf8>>, <<", ">>, <<"nocrlf">>])).
t_concat(_) ->
?assertEqual(<<"ab">>, apply_func(concat, [<<"a">>, <<"b">>])),
?assertEqual(<<"ab">>, apply_func('+', [<<"a">>, <<"b">>])),
?assertEqual(<<"哈哈你好"/utf8>>, apply_func(concat, [<<"哈哈"/utf8>>,<<"你好"/utf8>>])),
?assertEqual(<<"abc">>, apply_func(concat, [apply_func(concat, [<<"a">>, <<"b">>]), <<"c">>])),
?assertEqual(<<"a">>, apply_func(concat, [<<"">>, <<"a">>])),
?assertEqual(<<"a">>, apply_func(concat, [<<"a">>, <<"">>])),
?assertEqual(<<>>, apply_func(concat, [<<"">>, <<"">>])).
t_sprintf(_) ->
?assertEqual(<<"Hello Shawn!">>, apply_func(sprintf, [<<"Hello ~ts!">>, <<"Shawn">>])),
?assertEqual(<<"Name: ABC, Count: 2">>, apply_func(sprintf, [<<"Name: ~ts, Count: ~p">>, <<"ABC">>, 2])),
?assertEqual(<<"Name: ABC, Count: 2, Status: {ok,running}">>, apply_func(sprintf, [<<"Name: ~ts, Count: ~p, Status: ~p">>, <<"ABC">>, 2, {ok, running}])).
t_pad(_) ->
?assertEqual(<<"abc ">>, apply_func(pad, [<<"abc">>, 5])),
?assertEqual(<<"abc">>, apply_func(pad, [<<"abc">>, 0])),
?assertEqual(<<"abc ">>, apply_func(pad, [<<"abc">>, 5, <<"trailing">>])),
?assertEqual(<<"abc">>, apply_func(pad, [<<"abc">>, 0, <<"trailing">>])),
?assertEqual(<<" abc ">>, apply_func(pad, [<<"abc">>, 5, <<"both">>])),
?assertEqual(<<"abc">>, apply_func(pad, [<<"abc">>, 0, <<"both">>])),
?assertEqual(<<" abc">>, apply_func(pad, [<<"abc">>, 5, <<"leading">>])),
?assertEqual(<<"abc">>, apply_func(pad, [<<"abc">>, 0, <<"leading">>])).
t_pad_char(_) ->
?assertEqual(<<"abcee">>, apply_func(pad, [<<"abc">>, 5, <<"trailing">>, <<"e">>])),
?assertEqual(<<"abcexex">>, apply_func(pad, [<<"abc">>, 5, <<"trailing">>, <<"ex">>])),
?assertEqual(<<"eabce">>, apply_func(pad, [<<"abc">>, 5, <<"both">>, <<"e">>])),
?assertEqual(<<"exabcex">>, apply_func(pad, [<<"abc">>, 5, <<"both">>, <<"ex">>])),
?assertEqual(<<"eeabc">>, apply_func(pad, [<<"abc">>, 5, <<"leading">>, <<"e">>])),
?assertEqual(<<"exexabc">>, apply_func(pad, [<<"abc">>, 5, <<"leading">>, <<"ex">>])).
t_replace(_) ->
?assertEqual(<<"ab-c--">>, apply_func(replace, [<<"ab c ">>, <<" ">>, <<"-">>])),
?assertEqual(<<"ab::c::::">>, apply_func(replace, [<<"ab c ">>, <<" ">>, <<"::">>])),
?assertEqual(<<"ab-c--">>, apply_func(replace, [<<"ab c ">>, <<" ">>, <<"-">>, <<"all">>])),
?assertEqual(<<"ab-c ">>, apply_func(replace, [<<"ab c ">>, <<" ">>, <<"-">>, <<"leading">>])),
?assertEqual(<<"ab c -">>, apply_func(replace, [<<"ab c ">>, <<" ">>, <<"-">>, <<"trailing">>])).
t_ascii(_) ->
?assertEqual(97, apply_func(ascii, [<<"a">>])),
?assertEqual(97, apply_func(ascii, [<<"ab">>])).
t_find(_) ->
?assertEqual(<<"cbcd">>, apply_func(find, [<<"acbcd">>, <<"c">>])),
?assertEqual(<<"cbcd">>, apply_func(find, [<<"acbcd">>, <<"c">>, <<"leading">>])),
?assertEqual(<<"">>, apply_func(find, [<<"acbcd">>, <<"e">>])),
?assertEqual(<<"">>, apply_func(find, [<<"">>, <<"c">>])),
?assertEqual(<<"">>, apply_func(find, [<<"">>, <<"">>])).
t_find_trailing(_) ->
?assertEqual(<<"cd">>, apply_func(find, [<<"acbcd">>, <<"c">>, <<"trailing">>])),
?assertEqual(<<"">>, apply_func(find, [<<"acbcd">>, <<"e">>, <<"trailing">>])),
?assertEqual(<<"">>, apply_func(find, [<<"">>, <<"c">>, <<"trailing">>])),
?assertEqual(<<"">>, apply_func(find, [<<"">>, <<"">>, <<"trailing">>])).
t_regex_match(_) ->
?assertEqual(true, apply_func(regex_match, [<<"acbcd">>, <<"c">>])),
?assertEqual(true, apply_func(regex_match, [<<"acbcd">>, <<"(ac)+">>])),
?assertEqual(false, apply_func(regex_match, [<<"bcd">>, <<"(ac)+">>])),
?assertEqual(true, apply_func(regex_match, [<<>>, <<".*">>])),
?assertEqual(false, apply_func(regex_match, [<<>>, <<"[a-z]+">>])),
?assertEqual(true, apply_func(regex_match, [<<"exebd">>, <<"^[a-z]+$">>])),
?assertEqual(false, apply_func(regex_match, [<<"e2xebd">>, <<"^[a-z]+$">>])).
t_regex_replace(_) ->
?assertEqual(<<>>, apply_func(regex_replace, [<<>>, <<"c.*">>, <<"e">>])),
?assertEqual(<<"aebed">>, apply_func(regex_replace, [<<"accbcd">>, <<"c+">>, <<"e">>])),
?assertEqual(<<"a[cc]b[c]d">>, apply_func(regex_replace, [<<"accbcd">>, <<"c+">>, <<"[&]">>])).
ascii_string() -> list(range(0,127)).
bin(S) -> iolist_to_binary(S).
%%------------------------------------------------------------------------------
%% Test cases for array funcs
%%------------------------------------------------------------------------------
t_nth(_) ->
?assertEqual(2, apply_func(nth, [2, [1,2,3,4]])),
?assertEqual(4, apply_func(nth, [4, [1,2,3,4]])).
t_length(_) ->
?assertEqual(4, apply_func(length, [[1,2,3,4]])),
?assertEqual(0, apply_func(length, [[]])).
t_slice(_) ->
?assertEqual([1,2,3,4], apply_func(sublist, [4, [1,2,3,4]])),
?assertEqual([1,2], apply_func(sublist, [2, [1,2,3,4]])),
?assertEqual([4], apply_func(sublist, [4, 1, [1,2,3,4]])),
?assertEqual([4], apply_func(sublist, [4, 2, [1,2,3,4]])),
?assertEqual([], apply_func(sublist, [5, 2, [1,2,3,4]])),
?assertEqual([2,3], apply_func(sublist, [2, 2, [1,2,3,4]])),
?assertEqual([1], apply_func(sublist, [1, 1, [1,2,3,4]])).
t_first_last(_) ->
?assertEqual(1, apply_func(first, [[1,2,3,4]])),
?assertEqual(4, apply_func(last, [[1,2,3,4]])).
t_contains(_) ->
?assertEqual(true, apply_func(contains, [1, [1,2,3,4]])),
?assertEqual(true, apply_func(contains, [3, [1,2,3,4]])),
?assertEqual(true, apply_func(contains, [<<"a">>, [<<>>,<<"ab">>,3,<<"a">>]])),
?assertEqual(true, apply_func(contains, [#{a=>b}, [#{a=>1}, #{a=>b}]])),
?assertEqual(false, apply_func(contains, [#{a=>b}, [#{a=>1}]])),
?assertEqual(false, apply_func(contains, [3, [1, 2]])),
?assertEqual(false, apply_func(contains, [<<"c">>, [<<>>,<<"ab">>,3,<<"a">>]])).
t_map_get(_) ->
?assertEqual(1, apply_func(map_get, [<<"a">>, #{a => 1}])),
?assertEqual(undefined, apply_func(map_get, [<<"a">>, #{}])),
?assertEqual(1, apply_func(map_get, [<<"a.b">>, #{a => #{b => 1}}])),
?assertEqual(undefined, apply_func(map_get, [<<"a.c">>, #{a => #{b => 1}}])),
?assertEqual(undefined, apply_func(map_get, [<<"a">>, #{}])).
t_map_put(_) ->
?assertEqual(#{<<"a">> => 1}, apply_func(map_put, [<<"a">>, 1, #{}])),
?assertEqual(#{a => 2}, apply_func(map_put, [<<"a">>, 2, #{a => 1}])),
?assertEqual(#{<<"a">> => #{<<"b">> => 1}}, apply_func(map_put, [<<"a.b">>, 1, #{}])),
?assertEqual(#{a => #{b => 1, <<"c">> => 1}}, apply_func(map_put, [<<"a.c">>, 1, #{a => #{b => 1}}])),
?assertEqual(#{a => 2}, apply_func(map_put, [<<"a">>, 2, #{a => 1}])).
t_mget(_) ->
?assertEqual(1, apply_func(map_get, [<<"a">>, #{a => 1}])),
?assertEqual(1, apply_func(map_get, [<<"a">>, #{<<"a">> => 1}])),
?assertEqual(undefined, apply_func(map_get, [<<"a">>, #{}])).
t_mput(_) ->
?assertEqual(#{<<"a">> => 1}, apply_func(map_put, [<<"a">>, 1, #{}])),
?assertEqual(#{<<"a">> => 2}, apply_func(map_put, [<<"a">>, 2, #{<<"a">> => 1}])),
?assertEqual(#{a => 2}, apply_func(map_put, [<<"a">>, 2, #{a => 1}])).
t_bitsize(_) ->
?assertEqual(8, apply_func(bitsize, [<<"a">>])),
?assertEqual(4, apply_func(bitsize, [<<15:4>>])).
t_subbits(_) ->
?assertEqual(1, apply_func(subbits, [<<255:8>>, 1])),
?assertEqual(3, apply_func(subbits, [<<255:8>>, 2])),
?assertEqual(7, apply_func(subbits, [<<255:8>>, 3])),
?assertEqual(15, apply_func(subbits, [<<255:8>>, 4])),
?assertEqual(31, apply_func(subbits, [<<255:8>>, 5])),
?assertEqual(63, apply_func(subbits, [<<255:8>>, 6])),
?assertEqual(127, apply_func(subbits, [<<255:8>>, 7])),
?assertEqual(255, apply_func(subbits, [<<255:8>>, 8])).
t_subbits2(_) ->
?assertEqual(1, apply_func(subbits, [<<255:8>>, 1, 1])),
?assertEqual(3, apply_func(subbits, [<<255:8>>, 1, 2])),
?assertEqual(7, apply_func(subbits, [<<255:8>>, 1, 3])),
?assertEqual(15, apply_func(subbits, [<<255:8>>, 1, 4])),
?assertEqual(31, apply_func(subbits, [<<255:8>>, 1, 5])),
?assertEqual(63, apply_func(subbits, [<<255:8>>, 1, 6])),
?assertEqual(127, apply_func(subbits, [<<255:8>>, 1, 7])),
?assertEqual(255, apply_func(subbits, [<<255:8>>, 1, 8])).
t_subbits2_1(_) ->
?assertEqual(1, apply_func(subbits, [<<255:8>>, 2, 1])),
?assertEqual(3, apply_func(subbits, [<<255:8>>, 2, 2])),
?assertEqual(7, apply_func(subbits, [<<255:8>>, 2, 3])),
?assertEqual(15, apply_func(subbits, [<<255:8>>, 2, 4])),
?assertEqual(31, apply_func(subbits, [<<255:8>>, 2, 5])),
?assertEqual(63, apply_func(subbits, [<<255:8>>, 2, 6])),
?assertEqual(127, apply_func(subbits, [<<255:8>>, 2, 7])),
?assertEqual(127, apply_func(subbits, [<<255:8>>, 2, 8])).
t_subbits2_integer(_) ->
?assertEqual(456, apply_func(subbits, [<<456:32/integer>>, 1, 32, <<"integer">>, <<"signed">>, <<"big">>])),
?assertEqual(-456, apply_func(subbits, [<<-456:32/integer>>, 1, 32, <<"integer">>, <<"signed">>, <<"big">>])).
t_subbits2_float(_) ->
R = apply_func(subbits, [<<5.3:64/float>>, 1, 64, <<"float">>, <<"unsigned">>, <<"big">>]),
RL = (5.3 - R),
ct:pal(";;;;~p", [R]),
?assert( (RL >= 0 andalso RL < 0.0001) orelse (RL =< 0 andalso RL > -0.0001)),
R2 = apply_func(subbits, [<<-5.3:64/float>>, 1, 64, <<"float">>, <<"signed">>, <<"big">>]),
RL2 = (5.3 + R2),
ct:pal(";;;;~p", [R2]),
?assert( (RL2 >= 0 andalso RL2 < 0.0001) orelse (RL2 =< 0 andalso RL2 > -0.0001)).
%%------------------------------------------------------------------------------
%% Test cases for Hash funcs
%%------------------------------------------------------------------------------
t_hash_funcs(_) ->
?PROPTEST(prop_hash_fun).
prop_hash_fun() ->
?FORALL(S, binary(),
begin
(32 == byte_size(apply_func(md5, [S]))) andalso
(40 == byte_size(apply_func(sha, [S]))) andalso
(64 == byte_size(apply_func(sha256, [S])))
end).
%%------------------------------------------------------------------------------
%% Test cases for base64
%%------------------------------------------------------------------------------
t_base64_encode(_) ->
?PROPTEST(prop_base64_encode).
prop_base64_encode() ->
?FORALL(S, list(range(0, 255)),
begin
Bin = iolist_to_binary(S),
Bin == base64:decode(apply_func(base64_encode, [Bin]))
end).
%%--------------------------------------------------------------------
%% Date functions
%%--------------------------------------------------------------------
t_now_rfc3339(_) ->
?assert(is_integer(
calendar:rfc3339_to_system_time(
binary_to_list(apply_func(now_rfc3339, []))))).
t_now_rfc3339_1(_) ->
[?assert(is_integer(
calendar:rfc3339_to_system_time(
binary_to_list(apply_func(now_rfc3339, [atom_to_binary(Unit, utf8)])),
[{unit, Unit}])))
|| Unit <- [second,millisecond,microsecond,nanosecond]].
t_now_timestamp(_) ->
?assert(is_integer(apply_func(now_timestamp, []))).
t_now_timestamp_1(_) ->
[?assert(is_integer(
apply_func(now_timestamp, [atom_to_binary(Unit, utf8)])))
|| Unit <- [second,millisecond,microsecond,nanosecond]].
t_unix_ts_to_rfc3339(_) ->
[begin
BUnit = atom_to_binary(Unit, utf8),
Epoch = apply_func(now_timestamp, [BUnit]),
DateTime = apply_func(unix_ts_to_rfc3339, [Epoch, BUnit]),
?assertEqual(Epoch,
calendar:rfc3339_to_system_time(binary_to_list(DateTime), [{unit, Unit}]))
end || Unit <- [second,millisecond,microsecond,nanosecond]].
t_rfc3339_to_unix_ts(_) ->
[begin
BUnit = atom_to_binary(Unit, utf8),
Epoch = apply_func(now_timestamp, [BUnit]),
DateTime = apply_func(unix_ts_to_rfc3339, [Epoch, BUnit]),
?assertEqual(Epoch, emqx_rule_funcs:rfc3339_to_unix_ts(DateTime, BUnit))
end || Unit <- [second,millisecond,microsecond,nanosecond]].
%%------------------------------------------------------------------------------
%% Utility functions
%%------------------------------------------------------------------------------
apply_func(Name, Args) when is_atom(Name) ->
erlang:apply(emqx_rule_funcs, Name, Args);
apply_func(Fun, Args) when is_function(Fun) ->
erlang:apply(Fun, Args).
apply_func(Name, Args, Input) when is_map(Input) ->
apply_func(apply_func(Name, Args), [Input]);
apply_func(Name, Args, Msg) ->
apply_func(Name, Args, emqx_message:to_map(Msg)).
message() ->
emqx_message:set_flags(#{dup => false},
emqx_message:make(<<"clientid">>, 1, <<"topic/#">>, <<"payload">>)).
% t_contains_topic(_) ->
% error('TODO').
% t_contains_topic_match(_) ->
% error('TODO').
% t_div(_) ->
% error('TODO').
% t_mod(_) ->
% error('TODO').
% t_abs(_) ->
% error('TODO').
% t_acos(_) ->
% error('TODO').
% t_acosh(_) ->
% error('TODO').
% t_asin(_) ->
% error('TODO').
% t_asinh(_) ->
% error('TODO').
% t_atan(_) ->
% error('TODO').
% t_atanh(_) ->
% error('TODO').
% t_ceil(_) ->
% error('TODO').
% t_cos(_) ->
% error('TODO').
% t_cosh(_) ->
% error('TODO').
% t_exp(_) ->
% error('TODO').
% t_floor(_) ->
% error('TODO').
% t_fmod(_) ->
% error('TODO').
% t_log(_) ->
% error('TODO').
% t_log10(_) ->
% error('TODO').
% t_log2(_) ->
% error('TODO').
% t_power(_) ->
% error('TODO').
% t_round(_) ->
% error('TODO').
% t_sin(_) ->
% error('TODO').
% t_sinh(_) ->
% error('TODO').
% t_sqrt(_) ->
% error('TODO').
% t_tan(_) ->
% error('TODO').
% t_tanh(_) ->
% error('TODO').
% t_bitnot(_) ->
% error('TODO').
% t_bitand(_) ->
% error('TODO').
% t_bitor(_) ->
% error('TODO').
% t_bitxor(_) ->
% error('TODO').
% t_bitsl(_) ->
% error('TODO').
% t_bitsr(_) ->
% error('TODO').
% t_lower(_) ->
% error('TODO').
% t_ltrim(_) ->
% error('TODO').
% t_rtrim(_) ->
% error('TODO').
% t_upper(_) ->
% error('TODO').
% t_split(_) ->
% error('TODO').
% t_md5(_) ->
% error('TODO').
% t_sha(_) ->
% error('TODO').
% t_sha256(_) ->
% error('TODO').
% t_json_encode(_) ->
% error('TODO').
% t_json_decode(_) ->
% error('TODO').
%%------------------------------------------------------------------------------
%% CT functions
%%------------------------------------------------------------------------------
all() ->
IsTestCase = fun("t_" ++ _) -> true; (_) -> false end,
[F || {F, _A} <- module_info(exports), IsTestCase(atom_to_list(F))].
suite() ->
[{ct_hooks, [cth_surefire]}, {timetrap, {seconds, 30}}].