-- Expressions unit tests context("Rspamd expressions", function() local rspamd_expression = require "rspamd_expression" local rspamd_mempool = require "rspamd_mempool" local rspamd_regexp = require "rspamd_regexp" local split_re = rspamd_regexp.create('/\\s+|\\)|\\(/') local function parse_func(str) -- extract token till the first space character local token = str local t = split_re:split(str) if t then token = t[1] end -- Return token name return token end local atoms = { A = 1.0, B = 0, C = 1, D = 0, E = 1, F = 0, G = 0, H = 0, I = 0, J = 0, K = 0, } local function process_func(token, input) --print(token) local t = input[token] return t end local pool = rspamd_mempool.create() local cases = { {'A & (!B | C)', '(A) (B) ! (C) | &'}, {'A & B | !C', '(C) ! (A) (B) & |'}, {'A & (B | !C)', '(A) (B) (C) ! | &'}, {'A & B &', nil}, -- Unbalanced braces {'(((A))', nil}, -- Balanced braces {'(((A)))', '(A)'}, -- Plus and comparison operators {'A + B + C + D > 2', '(A) (B) (C) (D) +(4) 2 >'}, -- Plus and logic operators {'((A + B + C + D) > 2) & D', '(D) (A) (B) (C) (D) +(4) 2 > &'}, -- Associativity {'A | B | C & D & E', '(A) (B) (C) (D) (E) &(3) |(3)'}, -- More associativity {'1 | 0 & 0 | 0', '(1) (0) (0) (0) & |(3)'}, {'(A) & (B) & ((C) | (D) | (E) | (F))', '(A) (B) (C) (D) (E) (F) |(4) &(3)' }, -- Extra space {'A & B | ! C', '(C) ! (A) (B) & |'}, -- False minus {'A + B + -C', '(A) (B) (-C) +(3)'}, } for _,c in ipairs(cases) do test("Expression creation function: " .. c[1], function() local expr,err = rspamd_expression.create(c[1], {parse_func, process_func}, pool) if not c[2] then assert_nil(expr, "Should not be able to parse " .. c[1]) else assert_not_nil(expr, "Cannot parse " .. c[1] .. '; error: ' .. (err or 'wut??')) assert_equal(expr:to_string(), c[2], string.format("Evaluated expr to '%s', expected: '%s'", expr:to_string(), c[2])) end end) end -- Expression is destroyed when the corresponding pool is destroyed cases = { {'(E) && ((B + B + B + B) >= 1)', 0}, {'A & B | !C', 0}, {'A & (!B | C)', 1}, {'A + B + C + D + E + F >= 2', 1}, {'((A + B + C + D) > 1) & F', 0}, {'(A + B + C + D) > 1 && F || E', 1}, {'(A + B + C + D) > 100 && F || !E', 0}, {'F && ((A + B + C + D) > 1)', 0}, {'(E) && ((B + B + B + B) >= 1)', 0}, {'!!C', 1}, {'(B) & (D) & ((G) | (H) | (I) | (A))', 0}, {'A & C & (!D || !C || !E)', 1}, {'A & C & !(D || C || E)', 0}, {'A + B + C', 2}, {'A * 2.0 + B + C', 3}, {'A * 2.0 + B - C', 1}, {'A / 2.0 + B - C', -0.5}, } for _,c in ipairs(cases) do test("Expression process function: " .. c[1], function() local expr,err = rspamd_expression.create(c[1], {parse_func, process_func}, pool) assert_not_nil(expr, "Cannot parse " .. c[1] .. '; error: ' .. (err or 'wut??')) res = expr:process(atoms) assert_equal(res, c[2], string.format("Processed expr '%s'{%s} returned '%d', expected: '%d'", expr:to_string(), c[1], res, c[2])) end) end end)