%%-------------------------------------------------------------------- %% 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) -> Apps = emqx_cth_suite:start( [ emqx, emqx_conf, {emqx_rule_engine, "rule_engine {jq_function_default_timeout=10s}"} ], #{work_dir => emqx_cth_suite:work_dir(Config)} ), [{apps, Apps} | Config]. end_per_suite(Config) -> Apps = ?config(apps, Config), emqx_cth_suite:stop(Apps), 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_bin2hexstr_not_even_bytes(_) -> ?assertEqual(<<"0102">>, emqx_rule_funcs:bin2hexstr(<<1:5, 2>>)), ?assertEqual(<<"1002">>, emqx_rule_funcs:bin2hexstr(<<16:5, 2>>)), ?assertEqual(<<"1002">>, emqx_rule_funcs:bin2hexstr(<<16:8, 2>>)), ?assertEqual(<<"102">>, emqx_rule_funcs:bin2hexstr(<<1:4, 2>>)), ?assertEqual(<<"102">>, emqx_rule_funcs:bin2hexstr(<<1:3, 2>>)), ?assertEqual(<<"102">>, emqx_rule_funcs:bin2hexstr(<<1:1, 2>>)), ?assertEqual(<<"002">>, emqx_rule_funcs:bin2hexstr(<<2:1, 2>>)), ?assertEqual(<<"02">>, emqx_rule_funcs:bin2hexstr(<<2>>)), ?assertEqual(<<"2">>, emqx_rule_funcs:bin2hexstr(<<2:2>>)), ?assertEqual(<<"1121">>, emqx_rule_funcs:bin2hexstr(<<17, 33>>)), ?assertEqual(<<"01121">>, emqx_rule_funcs:bin2hexstr(<<17:9, 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+">>, <<"[&]">>])). t_unescape(_) -> ?assertEqual(<<"\n">> = <<10>>, emqx_rule_funcs:unescape(<<"\\n">>)), ?assertEqual(<<"\t">> = <<9>>, emqx_rule_funcs:unescape(<<"\\t">>)), ?assertEqual(<<"\r">> = <<13>>, emqx_rule_funcs:unescape(<<"\\r">>)), ?assertEqual(<<"\b">> = <<8>>, emqx_rule_funcs:unescape(<<"\\b">>)), ?assertEqual(<<"\f">> = <<12>>, emqx_rule_funcs:unescape(<<"\\f">>)), ?assertEqual(<<"\v">> = <<11>>, emqx_rule_funcs:unescape(<<"\\v">>)), ?assertEqual(<<"'">> = <<39>>, emqx_rule_funcs:unescape(<<"\\'">>)), ?assertEqual(<<"\"">> = <<34>>, emqx_rule_funcs:unescape(<<"\\\"">>)), ?assertEqual(<<"?">> = <<63>>, emqx_rule_funcs:unescape(<<"\\?">>)), ?assertEqual(<<7>>, emqx_rule_funcs:unescape(<<"\\a">>)), % Test escaping backslash itself ?assertEqual(<<"\\">> = <<92>>, emqx_rule_funcs:unescape(<<"\\\\">>)), % Test a string without any escape sequences ?assertEqual(<<"Hello, World!">>, emqx_rule_funcs:unescape(<<"Hello, World!">>)), % Test a string with escape sequences ?assertEqual(<<"Hello,\t World\n!">>, emqx_rule_funcs:unescape(<<"Hello,\\t World\\n!">>)), % Test unrecognized escape sequence (should throw an error) ?assertException( throw, {unrecognized_escape_sequence, <<$\\, $L>>}, emqx_rule_funcs:unescape(<<"\\L">>) ), % Test hexadecimal escape sequences % Newline ?assertEqual(<<"\n">>, emqx_rule_funcs:unescape(<<"\\x0A">>)), % Newline ?assertEqual(<<"hej\n">>, emqx_rule_funcs:unescape(<<"hej\\x0A">>)), % Newline ?assertEqual(<<"\nhej">>, emqx_rule_funcs:unescape(<<"\\x0Ahej">>)), % Newline ?assertEqual(<<"hej\nhej">>, emqx_rule_funcs:unescape(<<"hej\\x0Ahej">>)), % "ABC" ?assertEqual(<<"ABC">>, emqx_rule_funcs:unescape(<<"\\x41\\x42\\x43">>)), % "\xFF" = 255 in decimal ?assertEqual(<<"\xFF"/utf8>>, emqx_rule_funcs:unescape(<<"\\xFF">>)), % "W" = \x57 ?assertEqual(<<"Hello, World!">>, emqx_rule_funcs:unescape(<<"Hello, \\x57orld!">>)). t_unescape_hex(_) -> ?assertEqual(<<"A"/utf8>>, emqx_rule_funcs:unescape(<<"\\x41">>)), ?assertEqual(<<"Hello"/utf8>>, emqx_rule_funcs:unescape(<<"\\x48\\x65\\x6c\\x6c\\x6f">>)), ?assertEqual(<<"A"/utf8>>, emqx_rule_funcs:unescape(<<"\\x0041">>)), ?assertEqual(<<"€"/utf8>>, emqx_rule_funcs:unescape(<<"\\x20AC">>)), ?assertEqual(<<"❤"/utf8>>, emqx_rule_funcs:unescape(<<"\\x2764">>)), ?assertException( throw, {unrecognized_escape_sequence, <<"\\x">>}, emqx_rule_funcs:unescape(<<"\\xG1">>) ), ?assertException( throw, {invalid_unicode_character, _}, emqx_rule_funcs:unescape(<<"\\x11000000">>) ), ?assertEqual( <<"Hello, 世界"/utf8>>, emqx_rule_funcs:unescape(<<"Hello, \\x00004E16\\x0000754C">>) ). 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">>]) ). t_subbits_not_even_bytes(_) -> InputBin = apply_func(hexstr2bin, [<<"9F4E58">>]), SubbitsRes = apply_func(subbits, [InputBin, 1, 6, <<"bits">>, <<"unsigned">>, <<"big">>]), ?assertEqual(<<"27">>, apply_func(bin2hexstr, [SubbitsRes])). %%------------------------------------------------------------------------------ %% 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. t_map_to_redis_hset_args(_Config) -> Do = fun(Map) -> tl(emqx_rule_funcs:map_to_redis_hset_args(Map)) end, ?assertEqual([], Do(#{})), ?assertEqual([], Do(#{1 => 2})), ?assertEqual([<<"a">>, <<"1">>], Do(#{<<"a">> => 1, 3 => 4})), ?assertEqual([<<"a">>, <<"1.1">>], Do(#{<<"a">> => 1.1})), ?assertEqual([<<"a">>, <<"true">>], Do(#{<<"a">> => true})), ?assertEqual([<<"a">>, <<"false">>], Do(#{<<"a">> => false})), ?assertEqual([<<"a">>, <<"">>], Do(#{<<"a">> => <<"">>})), ?assertEqual([<<"a">>, <<"i j">>], Do(#{<<"a">> => <<"i j">>})), %% no determined ordering ?assert( case Do(#{<<"a">> => 1, <<"b">> => 2}) of [<<"a">>, <<"1">>, <<"b">>, <<"2">>] -> true; [<<"b">>, <<"2">>, <<"a">>, <<"1">>] -> true end ), ?assertEqual([], Do(<<"not json">>)), ?assertEqual([], Do([<<"not map">>, <<"not json either">>])), 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}}].