emqx/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl

1462 lines
50 KiB
Erlang

%%--------------------------------------------------------------------
%% Copyright (c) 2020-2024 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").
-define(PROPTEST(F), ?assert(proper:quickcheck(F()))).
%%-define(PROPTEST(F), ?assert(proper:quickcheck(F(), [{on_output, fun ct:print/2}]))).
init_per_suite(Config) ->
application:load(emqx_conf),
ConfigConf = <<"rule_engine {jq_function_default_timeout=10s}">>,
ok = emqx_common_test_helpers:load_config(emqx_rule_engine_schema, ConfigConf),
Config.
end_per_suite(_Config) ->
ok.
eventmsg_publish(Msg) ->
{Columns, _} = emqx_rule_events:eventmsg_publish(Msg),
Columns.
%%------------------------------------------------------------------------------
%% 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 Conversion 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(badarg, 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(badarg, 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(badarg, 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_float2str(_) ->
?assertEqual(<<"20.2">>, emqx_rule_funcs:float2str(20.2, 1)),
?assertEqual(<<"20.2">>, emqx_rule_funcs:float2str(20.2, 10)),
?assertEqual(<<"20.199999999999999">>, emqx_rule_funcs:float2str(20.2, 15)),
?assertEqual(<<"20.1999999999999993">>, emqx_rule_funcs:float2str(20.2, 16)).
t_hexstr2bin(_) ->
?assertEqual(<<6, 54, 79>>, emqx_rule_funcs:hexstr2bin(<<"6364f">>)),
?assertEqual(<<10>>, emqx_rule_funcs:hexstr2bin(<<"a">>)),
?assertEqual(<<15>>, emqx_rule_funcs:hexstr2bin(<<"f">>)),
?assertEqual(<<5>>, emqx_rule_funcs:hexstr2bin(<<"5">>)),
?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(false, emqx_rule_funcs:is_null(null)),
?assertEqual(true, emqx_rule_funcs:is_null(undefined)),
?assertEqual(false, 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_null_var(_) ->
?assertEqual(true, emqx_rule_funcs:is_null_var(null)),
?assertEqual(false, emqx_rule_funcs:is_null_var(<<"null">>)),
?assertEqual(true, emqx_rule_funcs:is_null_var(undefined)),
?assertEqual(false, emqx_rule_funcs:is_null_var(<<"undefined">>)),
?assertEqual(false, emqx_rule_funcs:is_null_var(a)),
?assertEqual(false, emqx_rule_funcs:is_null_var(<<>>)),
?assertEqual(false, emqx_rule_funcs:is_null_var(<<"a">>)).
t_is_not_null(_) ->
[
?assertEqual(emqx_rule_funcs:is_not_null(T), not emqx_rule_funcs:is_null(T))
|| T <- [undefined, <<"undefined">>, null, <<"null">>, a, <<"a">>, <<>>]
].
t_is_not_null_var(_) ->
[
?assertEqual(emqx_rule_funcs:is_not_null_var(T), not emqx_rule_funcs:is_null_var(T))
|| T <- [undefined, <<"undefined">>, null, <<"null">>, 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_join_to_string(_) ->
A = 1,
B = a,
C = <<"c">>,
D = #{a => 1},
E = [1, 2, 3],
F = [#{<<"key">> => 1, <<"value">> => 2}],
M = #{<<"a">> => a, <<"b">> => 1, <<"c">> => <<"c">>},
J = <<"{\"a\":\"a\",\"b\":1,\"c\":\"c\"}">>,
?assertEqual(<<"a,b,c">>, apply_func(join_to_string, [<<",">>, [<<"a">>, <<"b">>, <<"c">>]])),
?assertEqual(<<"a b c">>, apply_func(join_to_string, [<<" ">>, [<<"a">>, <<"b">>, <<"c">>]])),
?assertEqual(
<<"a, b, c">>, apply_func(join_to_string, [<<", ">>, [<<"a">>, <<"b">>, <<"c">>]])
),
?assertEqual(
<<"1, a, c, {\"a\":1}, [1,2,3], [{\"value\":2,\"key\":1}]">>,
apply_func(join_to_string, [<<", ">>, [A, B, C, D, E, F]])
),
?assertEqual(<<"a">>, apply_func(join_to_string, [<<",">>, [<<"a">>]])),
?assertEqual(<<"">>, apply_func(join_to_string, [<<",">>, []])),
?assertEqual(<<"a, b, c">>, apply_func(join_to_string, [emqx_rule_funcs:map_keys(M)])),
?assertEqual(<<"a, b, c">>, apply_func(join_to_string, [emqx_rule_funcs:map_keys(J)])),
?assertEqual(<<"a, 1, c">>, apply_func(join_to_string, [emqx_rule_funcs:map_values(M)])),
?assertEqual(<<"a, 1, c">>, apply_func(join_to_string, [emqx_rule_funcs:map_values(J)])).
t_join_to_sql_values_string(_) ->
A = 1,
B = a,
C = <<"c">>,
D = #{a => 1},
E = [1, 2, 3],
E1 = [97, 98],
F = [#{<<"key">> => 1, <<"value">> => 2}],
M = #{<<"a">> => a, <<"b">> => 1, <<"c">> => <<"c">>},
J = <<"{\"a\":\"a\",\"b\":1,\"c\":\"c\"}">>,
?assertEqual(
<<"'a', 'b', 'c'">>, apply_func(join_to_sql_values_string, [[<<"a">>, <<"b">>, <<"c">>]])
),
?assertEqual(
<<"1, 'a', 'c', '{\"a\":1}', '[1,2,3]', '[97,98]', '[{\"value\":2,\"key\":1}]'">>,
apply_func(join_to_sql_values_string, [[A, B, C, D, E, E1, F]])
),
?assertEqual(<<"'a'">>, apply_func(join_to_sql_values_string, [[<<"a">>]])),
?assertEqual(<<"">>, apply_func(join_to_sql_values_string, [[]])),
?assertEqual(
<<"'a', 'b', 'c'">>, apply_func(join_to_sql_values_string, [emqx_rule_funcs:map_keys(M)])
),
?assertEqual(
<<"'a', 'b', 'c'">>, apply_func(join_to_sql_values_string, [emqx_rule_funcs:map_keys(J)])
),
?assertEqual(
<<"'a', 1, 'c'">>, apply_func(join_to_sql_values_string, [emqx_rule_funcs:map_values(M)])
),
?assertEqual(
<<"'a', 1, 'c'">>, apply_func(join_to_sql_values_string, [emqx_rule_funcs:map_values(J)])
).
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+">>, <<"[&]">>])).
jq_1_elm_res(JSONString) ->
Bin = list_to_binary(JSONString),
[apply_func(json_decode, [Bin])].
t_jq(_) ->
?assertEqual(
jq_1_elm_res("{\"b\":2}"),
apply_func(jq, [<<".">>, apply_func(json_decode, [<<"{\"b\": 2}">>])])
),
?assertEqual(
jq_1_elm_res("6"),
apply_func(jq, [<<".+1">>, apply_func(json_decode, [<<"5">>])])
),
?assertEqual(
jq_1_elm_res("{\"b\":2}"),
apply_func(jq, [<<".">>, <<"{\"b\": 2}">>])
),
%% Expicitly set timeout
?assertEqual(
jq_1_elm_res("{\"b\":2}"),
apply_func(jq, [<<".">>, <<"{\"b\": 2}">>, 10000])
),
TOProgram = erlang:iolist_to_binary(
"def while(cond; update):"
" def _while:"
" if cond then (update | _while) else . end;"
" _while;"
"while(. < 42; . * 2)"
),
got_timeout =
try
apply_func(jq, [TOProgram, <<"-2">>, 10])
catch
throw:{jq_exception, {timeout, _}} ->
%% Got timeout as expected
got_timeout
end,
?assertThrow(
{jq_exception, {timeout, _}},
apply_func(jq, [TOProgram, <<"-2">>])
).
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(mget, [<<"a">>, #{a => 1}])),
?assertEqual(1, apply_func(mget, [<<"a">>, <<"{\"a\" : 1}">>])),
?assertEqual(1, apply_func(mget, [<<"a">>, #{<<"a">> => 1}])),
?assertEqual(1, apply_func(mget, [<<"a.b">>, #{<<"a.b">> => 1}])),
?assertEqual(undefined, apply_func(mget, [<<"a">>, #{}])).
t_mput(_) ->
?assertEqual(#{<<"a">> => 1}, apply_func(mput, [<<"a">>, 1, #{}])),
?assertEqual(#{<<"a">> => 2}, apply_func(mput, [<<"a">>, 2, #{<<"a">> => 1}])),
?assertEqual(#{<<"a">> => 2}, apply_func(mput, [<<"a">>, 2, <<"{\"a\" : 1}">>])),
?assertEqual(#{<<"a.b">> => 2}, apply_func(mput, [<<"a.b">>, 2, #{<<"a.b">> => 1}])),
?assertEqual(#{a => 2}, apply_func(mput, [<<"a">>, 2, #{a => 1}])).
t_map_to_entries(_) ->
?assertEqual([], apply_func(map_to_entries, [#{}])),
M = #{a => 1, b => <<"b">>},
J = <<"{\"a\":1,\"b\":\"b\"}">>,
?assertEqual(
[
#{key => a, value => 1},
#{key => b, value => <<"b">>}
],
apply_func(map_to_entries, [M])
),
?assertEqual(
[
#{key => <<"a">>, value => 1},
#{key => <<"b">>, value => <<"b">>}
],
apply_func(map_to_entries, [J])
).
t_bitsize(_) ->
?assertEqual(8, apply_func(bitsize, [<<"a">>])),
?assertEqual(4, apply_func(bitsize, [<<15:4>>])).
t_bytesize(_) ->
?assertEqual(1, apply_func(bytesize, [<<"a">>])),
?assertEqual(0, apply_func(bytesize, [<<>>])).
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)).
t_subbits_4_args(_) ->
R = apply_func(subbits, [<<5.3:64/float>>, 1, 64, <<"float">>]),
RL = (5.3 - R),
?assert((RL >= 0 andalso RL < 0.0001) orelse (RL =< 0 andalso RL > -0.0001)).
t_subbits_5_args(_) ->
?assertEqual(
456,
apply_func(subbits, [<<456:32/integer>>, 1, 32, <<"integer">>, <<"unsigned">>])
).
%%------------------------------------------------------------------------------
%% 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 zip funcs
%%------------------------------------------------------------------------------
t_zip_funcs(_) ->
?PROPTEST(prop_zip_fun).
prop_zip_fun() ->
?FORALL(
S,
binary(),
S == apply_func(unzip, [apply_func(zip, [S])])
).
%%------------------------------------------------------------------------------
%% Test cases for gzip funcs
%%------------------------------------------------------------------------------
t_gzip_funcs(_) ->
?PROPTEST(prop_gzip_fun).
prop_gzip_fun() ->
?FORALL(
S,
binary(),
S == apply_func(gunzip, [apply_func(gzip, [S])])
).
%%------------------------------------------------------------------------------
%% Test cases for zip funcs
%%------------------------------------------------------------------------------
t_zip_compress_funcs(_) ->
?PROPTEST(prop_zip_compress_fun).
prop_zip_compress_fun() ->
?FORALL(
S,
binary(),
S == apply_func(zip_uncompress, [apply_func(zip_compress, [S])])
).
%%------------------------------------------------------------------------------
%% 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]
].
t_format_date_funcs(_) ->
?PROPTEST(prop_format_date_fun).
prop_format_date_fun() ->
Args1 = [<<"second">>, <<"+07:00">>, <<"%m--%d--%Y---%H:%M:%S%z">>],
?FORALL(
S,
erlang:system_time(second),
S ==
apply_func(
date_to_unix_ts,
Args1 ++
[
apply_func(
format_date,
Args1 ++ [S]
)
]
)
),
Args2 = [<<"millisecond">>, <<"+04:00">>, <<"--%m--%d--%Y---%H:%M:%S:%3N%z">>],
Args2DTUS = [<<"millisecond">>, <<"--%m--%d--%Y---%H:%M:%S:%3N%z">>],
?FORALL(
S,
erlang:system_time(millisecond),
S ==
apply_func(
date_to_unix_ts,
Args2DTUS ++
[
apply_func(
format_date,
Args2 ++ [S]
)
]
)
),
Args = [<<"second">>, <<"+08:00">>, <<"%Y-%m-%d-%H:%M:%S%z">>],
ArgsDTUS = [<<"second">>, <<"%Y-%m-%d-%H:%M:%S%z">>],
?FORALL(
S,
erlang:system_time(second),
S ==
apply_func(
date_to_unix_ts,
ArgsDTUS ++
[
apply_func(
format_date,
Args ++ [S]
)
]
)
),
% no offset in format string. force add offset
Second = erlang:system_time(second),
Args3 = [<<"second">>, <<"+04:00">>, <<"--%m--%d--%Y---%H:%M:%S">>, Second],
Formatters3 = apply_func(format_date, Args3),
Args3DTUS = [<<"second">>, <<"+04:00">>, <<"--%m--%d--%Y---%H:%M:%S">>, Formatters3],
Second == apply_func(date_to_unix_ts, Args3DTUS).
t_timezone_to_offset_seconds(_) ->
timezone_to_offset_seconds_helper(timezone_to_offset_seconds),
%% The timezone_to_second function is kept for compatibility with 4.X.
timezone_to_offset_seconds_helper(timezone_to_second).
timezone_to_offset_seconds_helper(FunctionName) ->
?assertEqual(120 * 60, apply_func(FunctionName, [<<"+02:00:00">>])),
?assertEqual(-120 * 60, apply_func(FunctionName, [<<"-02:00:00">>])),
?assertEqual(102, apply_func(FunctionName, [<<"+00:01:42">>])),
?assertEqual(0, apply_func(FunctionName, [<<"z">>])),
?assertEqual(0, apply_func(FunctionName, [<<"Z">>])),
?assertEqual(42, apply_func(FunctionName, [42])),
?assertEqual(0, apply_func(FunctionName, [undefined])),
%% Check that the following does not crash
apply_func(FunctionName, [<<"local">>]),
apply_func(FunctionName, ["local"]),
apply_func(FunctionName, [local]),
ok.
t_date_to_unix_ts(_) ->
TestTab = [
{{"2024-03-01T10:30:38+08:00", second}, [
<<"second">>, <<"+08:00">>, <<"%Y-%m-%d %H-%M-%S">>, <<"2024-03-01 10:30:38">>
]},
{{"2024-03-01T10:30:38.333+08:00", second}, [
<<"second">>, <<"+08:00">>, <<"%Y-%m-%d %H-%M-%S.%3N">>, <<"2024-03-01 10:30:38.333">>
]},
{{"2024-03-01T10:30:38.333+08:00", millisecond}, [
<<"millisecond">>,
<<"+08:00">>,
<<"%Y-%m-%d %H-%M-%S.%3N">>,
<<"2024-03-01 10:30:38.333">>
]},
{{"2024-03-01T10:30:38.333+08:00", microsecond}, [
<<"microsecond">>,
<<"+08:00">>,
<<"%Y-%m-%d %H-%M-%S.%3N">>,
<<"2024-03-01 10:30:38.333">>
]},
{{"2024-03-01T10:30:38.333+08:00", nanosecond}, [
<<"nanosecond">>,
<<"+08:00">>,
<<"%Y-%m-%d %H-%M-%S.%3N">>,
<<"2024-03-01 10:30:38.333">>
]},
{{"2024-03-01T10:30:38.333444+08:00", microsecond}, [
<<"microsecond">>,
<<"+08:00">>,
<<"%Y-%m-%d %H-%M-%S.%6N">>,
<<"2024-03-01 10:30:38.333444">>
]}
],
lists:foreach(
fun({{DateTime3339, Unit}, DateToTsArgs}) ->
?assertEqual(
calendar:rfc3339_to_system_time(DateTime3339, [{unit, Unit}]),
apply_func(date_to_unix_ts, DateToTsArgs),
"Failed on test: " ++ DateTime3339 ++ "/" ++ atom_to_list(Unit)
)
end,
TestTab
).
t_parse_date_errors(_) ->
?assertError(
bad_formatter_or_date,
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-059999-26 10:40:12">>
)
),
?assertError(
bad_formatter_or_date,
emqx_rule_funcs:date_to_unix_ts(second, <<"%y-%m-%d %H:%M:%S">>, <<"2022-05-26 10:40:12">>)
),
%% invalid formats
?assertThrow(
{missing_date_part, month},
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%d %H:%M:%S">>, <<"2022-32 10:40:12">>
)
),
?assertThrow(
{missing_date_part, year},
emqx_rule_funcs:date_to_unix_ts(
second, <<"%H:%M:%S">>, <<"10:40:12">>
)
),
?assertError(
_,
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-32 10:40:12">>
)
),
?assertError(
_,
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2023-02-29 10:40:12">>
)
),
?assertError(
_,
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-30 10:40:12">>
)
),
%% Compatibility test
%% UTC+0
UnixTs = 1653561612,
?assertEqual(
UnixTs,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-26 10:40:12">>)
),
?assertEqual(
UnixTs,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H-%M-%S">>, <<"2022-05-26 10:40:12">>)
),
?assertEqual(
UnixTs,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-26 10-40-12">>)
),
%% leap year checks
?assertEqual(
%% UTC+0
1709217100,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-29 14:31:40">>)
),
?assertEqual(
%% UTC+0
1709297071,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-01 12:44:31">>)
),
?assertEqual(
%% UTC+0
4107588271,
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2100-03-01 12:44:31">>)
),
?assertEqual(
%% UTC+8
1709188300,
emqx_rule_funcs:date_to_unix_ts(
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-29 14:31:40">>
)
),
?assertEqual(
%% UTC+8
1709268271,
emqx_rule_funcs:date_to_unix_ts(
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-01 12:44:31">>
)
),
?assertEqual(
%% UTC+8
4107559471,
emqx_rule_funcs:date_to_unix_ts(
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2100-03-01 12:44:31">>
)
),
%% None zero zone shift with millisecond level precision
Tz1 = calendar:rfc3339_to_system_time("2024-02-23T15:00:00.123+08:00", [{unit, second}]),
?assertEqual(
Tz1,
emqx_rule_funcs:date_to_unix_ts(
second, <<"%Y-%m-%d %H:%M:%S.%3N%:z">>, <<"2024-02-23 15:00:00.123+08:00">>
)
),
ok.
%%------------------------------------------------------------------------------
%% 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}}].