--- /dev/null
+/* Copyright (c) 2021 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "str.h"
+#include "strescape.h"
+#include "event-filter.h"
+#include "event-filter-private.h"
+
+#define STRING1 "X"
+#define STRING2 "Y"
+
+/* dummy values, at least for now */
+#define SOURCE_FILENAME "blah.c"
+#define SOURCE_LINE 123
+
+static void check_expr(struct event *event,
+ struct event_filter *filter,
+ enum event_filter_log_type log_type,
+ bool expected)
+{
+ struct event_filter_node *expr;
+ unsigned int num_queries;
+ bool got;
+
+ /* get at the expr inside the filter */
+ expr = event_filter_get_expr_for_testing(filter, &num_queries);
+ test_assert(num_queries == 1); /* should have only one query */
+
+ got = event_filter_query_match_eval(expr, event,
+ SOURCE_FILENAME, SOURCE_LINE,
+ log_type);
+ test_assert(got == expected);
+}
+
+static void do_test_expr(const char *filter_string, struct event *event,
+ enum event_filter_log_type log_type,
+ bool expected)
+{
+ const char *error;
+
+ test_begin(t_strdup_printf("%.*s log type + event {a=%s, b=%s} + filter '%s' (exp %s)",
+ 3, /* truncate the type name to avoid CI seeing 'warning' messages */
+ event_filter_category_from_log_type(log_type),
+ event_find_field_str(event, "a"),
+ event_find_field_str(event, "b"),
+ filter_string,
+ expected ? "true" : "false"));
+
+ /* set up the filter expression */
+ struct event_filter *filter = event_filter_create();
+ test_assert(event_filter_parse(filter_string, filter, &error) == 0);
+
+ check_expr(event, filter, log_type, expected);
+
+ test_end();
+}
+
+static void test_unary_expr(struct event *event,
+ const char *expr, bool truth,
+ enum event_filter_log_type log_type)
+{
+ /*
+ * The UNARY() macro checks:
+ *
+ * 1. expr
+ * 2. NOT expr
+ * 3. NOT (expr)
+ *
+ * Note that numbers 2 and 3 are equivalent.
+ *
+ * The truth argument specifies the expected truth-iness of the
+ * passed in expression.
+ */
+#define UNARY() \
+ T_BEGIN { \
+ do_test_expr(expr, \
+ event, log_type, truth); \
+ do_test_expr(t_strdup_printf("NOT %s", expr), \
+ event, log_type, !truth); \
+ do_test_expr(t_strdup_printf("NOT (%s)", expr), \
+ event, log_type, !truth); \
+ } T_END
+
+ UNARY();
+}
+
+static void test_binary_expr(struct event *event,
+ const char *expr1, const char *expr2,
+ bool truth1, bool truth2,
+ enum event_filter_log_type log_type)
+{
+ /*
+ * The BINARY() macro checks:
+ *
+ * 1. expr1 op expr2
+ * 2. NOT expr1 op expr2
+ * 3. NOT (expr1) op expr2
+ * 4. (NOT expr1) op expr2
+ * 5. expr1 op NOT expr2
+ * 6. expr1 op NOT (expr2)
+ * 7. expr1 op (NOT expr2)
+ * 8. NOT (expr1 op expr2)
+ * 9. NOT expr1 op NOT expr2
+ * 10. NOT (expr1) op NOT (expr2)
+ * 11. (NOT expr1) op (NOT expr2)
+ *
+ * Where op is OR or AND.
+ *
+ * Note that:
+ * - numbers 2, 3, and 4 are equivalent
+ * - numbers 5, 6, and 7 are equivalent
+ * - numbers 9, 10, and 11 are equivalent
+ *
+ * The truth arugments specify the expected truth-iness of the
+ * passed in expressions.
+ */
+#define BINARY(opstr, op) \
+ T_BEGIN { \
+ do_test_expr(t_strdup_printf("%s %s %s", expr1, opstr, expr2),\
+ event, log_type, \
+ (truth1) op (truth2)); \
+ do_test_expr(t_strdup_printf("NOT %s %s %s", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op (truth2)); \
+ do_test_expr(t_strdup_printf("NOT (%s) %s %s", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op (truth2)); \
+ do_test_expr(t_strdup_printf("(NOT %s) %s %s", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op (truth2)); \
+ do_test_expr(t_strdup_printf("%s %s NOT %s", expr1, opstr, expr2),\
+ event, log_type, \
+ (truth1) op !(truth2)); \
+ do_test_expr(t_strdup_printf("%s %s NOT (%s)", expr1, opstr, expr2),\
+ event, log_type, \
+ (truth1) op !(truth2)); \
+ do_test_expr(t_strdup_printf("%s %s (NOT %s)", expr1, opstr, expr2),\
+ event, log_type, \
+ (truth1) op !(truth2)); \
+ do_test_expr(t_strdup_printf("NOT (%s %s %s)", expr1, opstr, expr2),\
+ event, log_type, \
+ !((truth1) op (truth2))); \
+ do_test_expr(t_strdup_printf("NOT %s %s NOT %s", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op !(truth2)); \
+ do_test_expr(t_strdup_printf("NOT (%s) %s NOT (%s)", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op !(truth2)); \
+ do_test_expr(t_strdup_printf("(NOT %s) %s (NOT %s)", expr1, opstr, expr2),\
+ event, log_type, \
+ !(truth1) op !(truth2)); \
+ } T_END
+
+ BINARY("OR", ||);
+ BINARY("AND", &&);
+}
+
+static void test_event_filter_expr_fields(enum event_filter_log_type log_type)
+{
+ static const char *values[] = {
+ NULL,
+ "",
+ STRING1,
+ STRING2,
+ };
+ unsigned int a, b;
+
+#define STR_IS_EMPTY(v) \
+ (((v) == NULL) || (strcmp("", (v)) == 0))
+#define STR_MATCHES(v, c) \
+ (((v) != NULL) && (strcmp((c), (v)) == 0))
+
+ /* unary */
+ for (a = 0; a < N_ELEMENTS(values); a++) {
+ /* set up the event to match against */
+ struct event *event = event_create(NULL);
+ event_add_str(event, "a", values[a]);
+
+ test_unary_expr(event,
+ "a=\"\"",
+ STR_IS_EMPTY(values[a]),
+ log_type);
+ test_unary_expr(event,
+ "a=" STRING1,
+ STR_MATCHES(values[a], STRING1),
+ log_type);
+
+ event_unref(&event);
+ }
+
+ /* binary */
+ for (a = 0; a < N_ELEMENTS(values); a++) {
+ for (b = 0; b < N_ELEMENTS(values); b++) {
+ /* set up the event to match against */
+ struct event *event = event_create(NULL);
+ event_add_str(event, "a", values[a]);
+ event_add_str(event, "b", values[b]);
+
+ test_binary_expr(event,
+ "a=\"\"",
+ "b=\"\"",
+ STR_IS_EMPTY(values[a]),
+ STR_IS_EMPTY(values[b]),
+ log_type);
+ test_binary_expr(event,
+ "a=" STRING1,
+ "b=\"\"",
+ STR_MATCHES(values[a], STRING1),
+ STR_IS_EMPTY(values[b]),
+ log_type);
+ test_binary_expr(event,
+ "a=\"\"",
+ "b=" STRING2,
+ STR_IS_EMPTY(values[a]),
+ STR_MATCHES(values[b], STRING2),
+ log_type);
+ test_binary_expr(event,
+ "a=" STRING1,
+ "b=" STRING2,
+ STR_MATCHES(values[a], STRING1),
+ STR_MATCHES(values[b], STRING2),
+ log_type);
+
+ event_unref(&event);
+ }
+ }
+}
+
+void test_event_filter_expr(void)
+{
+ static const enum event_filter_log_type log_types[] = {
+ EVENT_FILTER_LOG_TYPE_DEBUG,
+ EVENT_FILTER_LOG_TYPE_INFO,
+ EVENT_FILTER_LOG_TYPE_WARNING,
+ EVENT_FILTER_LOG_TYPE_ERROR,
+ EVENT_FILTER_LOG_TYPE_FATAL,
+ EVENT_FILTER_LOG_TYPE_PANIC,
+ };
+ unsigned int i;
+
+ for (i = 0; i < N_ELEMENTS(log_types); i++)
+ test_event_filter_expr_fields(log_types[i]);
+}