summaryrefslogtreecommitdiffstats
path: root/src/lib-master/test-event-stats.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-master/test-event-stats.c')
-rw-r--r--src/lib-master/test-event-stats.c784
1 files changed, 784 insertions, 0 deletions
diff --git a/src/lib-master/test-event-stats.c b/src/lib-master/test-event-stats.c
new file mode 100644
index 0000000..565118b
--- /dev/null
+++ b/src/lib-master/test-event-stats.c
@@ -0,0 +1,784 @@
+/* Copyright (c) 2018 Dovecot authors, see the included COPYING file */
+
+#include "test-lib.h"
+#include "lib.h"
+#include "time-util.h"
+#include "lib-event-private.h"
+#include "str.h"
+#include "sleep.h"
+#include "ioloop.h"
+#include "connection.h"
+#include "ostream.h"
+#include "istream.h"
+#include "stats-client.h"
+#include "test-common.h"
+#include <fcntl.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#define TST_BEGIN(test_name) \
+ test_begin(test_name); \
+ ioloop_timeval.tv_sec = 0; \
+ ioloop_timeval.tv_usec = 0;
+
+#define BASE_DIR "."
+#define SOCK_PATH ".test-temp-stats-event-sock"
+
+#define SOCK_FULL BASE_DIR "/" SOCK_PATH
+
+static struct event_category test_cats[5] = {
+ {.name = "test1"},
+ {.name = "test2"},
+ {.name = "test3"},
+ {.name = "test4"},
+ {.name = "test5"},
+};
+
+static struct event_field test_fields[5] = {
+ {.key = "key1",
+ .value_type = EVENT_FIELD_VALUE_TYPE_STR,
+ .value = {.str = "str1"}},
+
+ {.key = "key2",
+ .value_type = EVENT_FIELD_VALUE_TYPE_INTMAX,
+ .value = {.intmax = 20}},
+
+ {.key = "key3",
+ .value_type = EVENT_FIELD_VALUE_TYPE_TIMEVAL,
+ .value = {.timeval = {.tv_sec = 10}}},
+
+ {.key = "key4",
+ .value = {.str = "str4"}},
+
+ {.key = "key5",
+ .value = {.intmax = 50}},
+};
+
+static void stats_conn_accept(void *context ATTR_UNUSED);
+static void stats_conn_destroy(struct connection *_conn);
+static void stats_conn_input(struct connection *_conn);
+
+static bool compare_test_stats_to(const char *format, ...) ATTR_FORMAT(1, 2);
+
+static struct connection_settings stats_conn_set = {
+ .input_max_size = SIZE_MAX,
+ .output_max_size = SIZE_MAX,
+ .client = FALSE
+};
+
+static const struct connection_vfuncs stats_conn_vfuncs = {
+ .destroy = stats_conn_destroy,
+ .input = stats_conn_input
+};
+
+struct server_connection {
+ struct connection conn;
+
+ pool_t pool;
+ bool handshake_sent:1;
+};
+
+static int stats_sock_fd;
+static struct connection_list *stats_conn_list;
+static struct ioloop *ioloop;
+
+static pid_t stats_pid;
+
+static int run_tests(void);
+static void signal_process(const char *signal_file);
+static void wait_for_signal(const char *signal_file);
+static void kill_stats_child(void);
+
+static const char *stats_ready = ".test-temp-stats-event-stats-ready";
+static const char *test_done = ".test-temp-stats-event-test-done";
+static const char *exit_stats = ".test-temp-stats-event-exit-stats";
+static const char *stats_data_file = ".test-temp-stats-event-test_stats";
+
+static void kill_stats_child(void)
+{
+ i_assert(stats_pid != 0);
+ (void)kill(stats_pid, SIGKILL);
+ (void)waitpid(stats_pid, NULL, 0);
+}
+
+static void stats_proc(void)
+{
+ struct io *io_listen;
+ /* Make sure socket file not existing */
+ i_unlink_if_exists(SOCK_FULL);
+ stats_sock_fd = net_listen_unix(SOCK_FULL, 128);
+ if (stats_sock_fd == -1)
+ i_fatal("listen(%s) failed: %m", SOCK_FULL);
+ ioloop = io_loop_create();
+ io_listen = io_add(stats_sock_fd, IO_READ, stats_conn_accept, NULL);
+ stats_conn_list = connection_list_init(&stats_conn_set,
+ &stats_conn_vfuncs);
+ signal_process(stats_ready);
+ io_loop_run(ioloop);
+ io_remove(&io_listen);
+ connection_list_deinit(&stats_conn_list);
+ io_loop_destroy(&ioloop);
+ i_close_fd(&stats_sock_fd);
+ i_unlink(SOCK_FULL);
+}
+
+static void stats_conn_accept(void *context ATTR_UNUSED)
+{
+ int fd;
+ struct server_connection *conn;
+ pool_t pool;
+ fd = net_accept(stats_sock_fd, NULL, NULL);
+ if (stats_sock_fd == -1)
+ return;
+ if (stats_sock_fd == -2)
+ i_fatal("test stats: accept() failed: %m");
+ net_set_nonblock(fd, TRUE);
+ pool = pool_alloconly_create("stats connection", 512);
+ conn = p_new(pool, struct server_connection, 1);
+ conn->pool = pool;
+ connection_init_server(stats_conn_list,
+ &conn->conn,
+ "stats connection", fd, fd);
+}
+
+static void stats_conn_destroy(struct connection *_conn)
+{
+ struct server_connection *conn =
+ (struct server_connection *)_conn;
+ connection_deinit(&conn->conn);
+ pool_unref(&conn->pool);
+}
+
+static void stats_conn_input(struct connection *_conn)
+{
+ int fd;
+ struct ostream *stats_data_out;
+ struct server_connection *conn = (struct server_connection *)_conn;
+ const char *handshake = "VERSION\tstats-server\t4\t0\n"
+ "FILTER\tcategory=test1 OR category=test2 OR category=test3 OR "
+ "category=test4 OR category=test5\n";
+ const char *line = NULL;
+ if (!conn->handshake_sent) {
+ conn->handshake_sent = TRUE;
+ o_stream_nsend_str(conn->conn.output, handshake);
+ }
+ while (access(exit_stats, F_OK) < 0) {
+ /* Test process haven't signal yet about end of the tests */
+ while (access(test_done, F_OK) < 0 ||
+ ((line=i_stream_read_next_line(conn->conn.input)) != NULL)) {
+ if (line != NULL) {
+ if (str_begins(line, "VERSION"))
+ continue;
+
+ if ((fd=open(stats_data_file, O_WRONLY | O_CREAT | O_APPEND, 0600)) < 0) {
+ i_fatal("failed create stats data file %m");
+ }
+
+ stats_data_out = o_stream_create_fd_autoclose(&fd, SIZE_MAX);
+ o_stream_nsend_str(stats_data_out, line);
+ o_stream_nsend_str(stats_data_out, "\n");
+
+ o_stream_set_no_error_handling(stats_data_out, TRUE);
+ o_stream_unref(&stats_data_out);
+ }
+ i_sleep_msecs(100);
+ }
+ i_unlink(test_done);
+ signal_process(stats_ready);
+ }
+ i_unlink(exit_stats);
+ i_unlink_if_exists(test_done);
+ io_loop_stop(ioloop);
+}
+
+static void wait_for_signal(const char *signal_file)
+{
+ struct timeval start, now;
+ i_gettimeofday(&start);
+ while (access(signal_file, F_OK) < 0) {
+ i_sleep_msecs(10);
+ i_gettimeofday(&now);
+ if (timeval_diff_usecs(&now, &start) > 10000000) {
+ kill_stats_child();
+ i_fatal("wait_for_signal has timed out");
+ }
+ }
+ i_unlink(signal_file);
+}
+
+static void signal_process(const char *signal_file)
+{
+ int fd;
+ if ((fd = open(signal_file, O_CREAT, 0666)) < 0) {
+ if (stats_pid != 0) {
+ kill_stats_child();
+ }
+ i_fatal("Failed to create signal file %s", signal_file);
+ }
+ i_close_fd(&fd);
+}
+
+static bool compare_test_stats_data_line(const char *reference, const char *actual)
+{
+ const char *const *ref_args = t_strsplit(reference, "\t");
+ const char *const *act_args = t_strsplit(actual, "\t");
+ unsigned int max = str_array_length(ref_args);
+
+ /* different lengths imply not equal */
+ if (str_array_length(ref_args) != str_array_length(act_args))
+ return FALSE;
+
+ for (size_t i = 0; i < max; i++) {
+ if (i > 1 && i < 6) continue;
+ if (*(ref_args[i]) == 'l') {
+ i++;
+ continue;
+ }
+ if (strcmp(ref_args[i], act_args[i]) != 0) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static bool compare_test_stats_data_lines(const char *actual, const char *reference)
+{
+ const char *const *lines_ref = t_strsplit(reference, "\n");
+ const char *const *lines_act = t_strsplit(actual, "\n");
+ for(size_t i = 0; *lines_ref != NULL && *lines_act != NULL; i++, lines_ref++, lines_act++) {
+ if (!compare_test_stats_data_line(*lines_ref, *lines_act))
+ return FALSE;
+ }
+ return *lines_ref == *lines_act;
+}
+
+static bool compare_test_stats_to(const char *format, ...)
+{
+ bool res;
+ string_t *reference = t_str_new(1024);
+ struct istream *input;
+ va_list args;
+ va_start (args, format);
+ str_vprintfa (reference, format, args);
+ va_end (args);
+ /* signal stats process to receive and record stats data */
+ signal_process(test_done);
+ /* Wait stats data to be recorded by stats process */
+ wait_for_signal(stats_ready);
+
+ input = i_stream_create_file(stats_data_file, SIZE_MAX);
+ while (i_stream_read(input) > 0) ;
+ if (input->stream_errno != 0) {
+ i_fatal("stats data file read failed: %s",
+ i_stream_get_error(input));
+ res = FALSE;
+ } else {
+ size_t size;
+ const unsigned char *data = i_stream_get_data(input, &size);
+ res = compare_test_stats_data_lines(t_strdup_until(data, data+size), str_c(reference));
+ }
+ i_stream_unref(&input);
+ i_unlink(stats_data_file);
+ return res;
+}
+
+static void test_fail_callback(const struct failure_context *ctx ATTR_UNUSED,
+ const char *format ATTR_UNUSED,
+ va_list args ATTR_UNUSED)
+{
+ /* ignore message, all we need is stats */
+}
+
+static void register_all_categories(void)
+{
+ /* Run this before all the tests,
+ so stats client doesn't send CATEGORY\ttestx anymore,
+ so test will produce stats records independent of test order */
+ struct event *ev;
+ int i;
+ for (i = 0; i < 5; i++) {
+ ev = event_create(NULL);
+ event_add_category(ev, &test_cats[i]);
+ e_info(ev, "message");
+ event_unref(&ev);
+ }
+ signal_process(test_done);
+}
+
+static void test_no_merging1(void)
+{
+ /* NULL parent */
+ int l;
+ TST_BEGIN("no merging parent is NULL");
+ struct event *single_ev = event_create(NULL);
+ event_add_category(single_ev, &test_cats[0]);
+ event_add_str(single_ev, test_fields[0].key, test_fields[0].value.str);
+ e_info(single_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&single_ev);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 0 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest1 Skey1 str1\n", l));
+ test_end();
+}
+
+static void test_no_merging2(void)
+{
+ /* Parent sent to stats */
+ int l;
+ uint64_t id;
+ TST_BEGIN("no merging parent sent to stats");
+ struct event *parent_ev = event_create(NULL);
+ event_add_category(parent_ev, &test_cats[0]);
+ parent_ev->sent_to_stats_id = parent_ev->change_id;
+ id = parent_ev->id;
+ struct event *child_ev = event_create(parent_ev);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_ev);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 %"PRIu64" 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest2\n"
+ "END 9\n", id, l));
+ test_end();
+}
+
+static void test_no_merging3(void)
+{
+ /* Parent have different timestamp */
+ int l, lp;
+ uint64_t idp;
+ TST_BEGIN("no merging parent timestamp differs");
+ struct event *parent_ev = event_create(NULL);
+ lp = __LINE__ - 1;
+ idp = parent_ev->id;
+ event_add_category(parent_ev, &test_cats[0]);
+ parent_ev->sent_to_stats_id = 0;
+ ioloop_timeval.tv_sec++;
+ struct event *child_ev = event_create(parent_ev);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_ev);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "END\t%"PRIu64"\n", idp, lp, idp, l, idp));
+ test_end();
+}
+
+static void test_merge_events1(void)
+{
+ int l;
+ TST_BEGIN("merge events parent NULL");
+ struct event *merge_ev1 = event_create(NULL);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_category(merge_ev1, &test_cats[1]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ event_add_int(merge_ev1,test_fields[1].key, test_fields[1].value.intmax);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_category(merge_ev2, &test_cats[2]);
+ event_add_category(merge_ev2, &test_cats[1]);
+ event_add_timeval(merge_ev2,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+ e_info(merge_ev2, "info message");
+ l = __LINE__ - 1;
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 0 1 0 0"
+ " s"__FILE__" %d l0 0"
+ " ctest3 ctest2 ctest1 Tkey3"
+ " 10 0 Ikey2 20"
+ " Skey1 str1\n", l));
+ test_end();
+}
+
+static void test_merge_events2(void)
+{
+ int l;
+ uint64_t id;
+ TST_BEGIN("merge events parent sent to stats");
+ struct event *parent_ev = event_create(NULL);
+ event_add_category(parent_ev, &test_cats[3]);
+ parent_ev->sent_to_stats_id = parent_ev->change_id;
+ struct event *merge_ev1 = event_create(parent_ev);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_category(merge_ev1, &test_cats[1]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ event_add_int(merge_ev1,test_fields[1].key, test_fields[1].value.intmax);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_category(merge_ev2, &test_cats[2]);
+ event_add_category(merge_ev2, &test_cats[1]);
+ event_add_timeval(merge_ev2,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+ e_info(merge_ev2, "info message");
+ l = __LINE__ - 1;
+ id = parent_ev->id;
+ event_unref(&parent_ev);
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ test_assert(
+ compare_test_stats_to(
+ "EVENT 0 %"PRIu64" 1 0 0"
+ " s"__FILE__" %d l0 0"
+ " ctest3 ctest2 ctest1 Tkey3"
+ " 10 0 Ikey2 20"
+ " Skey1 str1\n"
+ "END 16\n", id, l));
+ test_end();
+}
+
+static void test_skip_parents(void)
+{
+ int l, lp;
+ uint64_t id;
+ TST_BEGIN("skip empty parents");
+ struct event *parent_to_log = event_create(NULL);
+ lp = __LINE__ - 1;
+ id = parent_to_log->id;
+ event_add_category(parent_to_log, &test_cats[0]);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent1 = event_create(parent_to_log);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent2 = event_create(empty_parent1);
+ ioloop_timeval.tv_sec++;
+ struct event *child_ev = event_create(empty_parent2);
+ event_add_category(child_ev, &test_cats[1]);
+ e_info(child_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_to_log);
+ event_unref(&empty_parent1);
+ event_unref(&empty_parent2);
+ event_unref(&child_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 3 0 "
+ "s"__FILE__" %d l3 0"
+ " ctest2\nEND\t%"PRIu64"\n", id, lp, id, l, id));
+ test_end();
+}
+
+static void test_merge_events_skip_parents(void)
+{
+ int lp, l;
+ uint64_t id;
+ TST_BEGIN("merge events and skip empty parents");
+ struct event *parent_to_log = event_create(NULL);
+ lp = __LINE__ - 1;
+ id = parent_to_log->id;
+ event_add_category(parent_to_log, &test_cats[0]);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent1 = event_create(parent_to_log);
+ ioloop_timeval.tv_sec++;
+ struct event *empty_parent2 = event_create(empty_parent1);
+ ioloop_timeval.tv_sec++;
+ struct event *child1_ev = event_create(empty_parent2);
+ event_add_category(child1_ev, &test_cats[1]);
+ event_add_category(child1_ev, &test_cats[2]);
+ event_add_int(child1_ev,test_fields[1].key, test_fields[1].value.intmax);
+ event_add_str(child1_ev,test_fields[0].key, test_fields[0].value.str);
+ struct event *child2_ev = event_create(empty_parent2);
+ event_add_category(child2_ev, &test_cats[3]);
+ event_add_category(child2_ev, &test_cats[4]);
+ event_add_timeval(child2_ev,test_fields[2].key,
+ &test_fields[2].value.timeval);
+ event_add_str(child2_ev,test_fields[3].key, test_fields[3].value.str);
+ e_info(child2_ev, "info message");
+ l = __LINE__ - 1;
+ event_unref(&parent_to_log);
+ event_unref(&empty_parent1);
+ event_unref(&empty_parent2);
+ event_unref(&child1_ev);
+ event_unref(&child2_ev);
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1\n"
+ "EVENT 0 %"PRIu64" 1 3 0 "
+ "s"__FILE__" %d l3 0 "
+ "ctest4 ctest5 Tkey3 10 0 Skey4"
+ " str4\nEND\t%"PRIu64"\n", id, lp, id, l, id));
+ test_end();
+}
+
+static struct event *make_event(struct event *parent,
+ struct event_category *cat,
+ int *line_r, uint64_t *id_r)
+{
+ struct event *event;
+ int line;
+
+ event = event_create(parent);
+ line = __LINE__ -1;
+
+ if (line_r != NULL)
+ *line_r = line;
+ if (id_r != NULL)
+ *id_r = event->id;
+
+ /* something in the test infrastructure assumes that at least one
+ category is always present - make it happy */
+ event_add_category(event, cat);
+
+ /* advance the clock to avoid event sending optimizations */
+ ioloop_timeval.tv_sec++;
+
+ return event;
+}
+
+static void test_parent_update_post_send(void)
+{
+ struct event *a, *b, *c;
+ uint64_t id;
+ int line, line_log1, line_log2;
+
+ TST_BEGIN("parent updated after send");
+
+ a = make_event(NULL, &test_cats[0], &line, &id);
+ b = make_event(a, &test_cats[1], NULL, NULL);
+ c = make_event(b, &test_cats[2], NULL, NULL);
+
+ /* set initial field values */
+ event_add_int(a, "a", 1);
+ event_add_int(b, "b", 2);
+ event_add_int(c, "c", 3);
+
+ /* force 'a' event to be sent */
+ e_info(b, "field 'a' should be 1");
+ line_log1 = __LINE__ - 1;
+
+ event_add_int(a, "a", 1000); /* update parent */
+
+ /* log child, which should re-sent parent */
+ e_info(c, "field 'a' should be 1000");
+ line_log2 = __LINE__ - 1;
+
+ event_unref(&a);
+ event_unref(&b);
+ event_unref(&c);
+
+ /* EVENT <parent> <type> ... */
+ /* BEGIN <id> <parent> <type> ... */
+ /* END <id> */
+ test_assert(
+ compare_test_stats_to(
+ /* first e_info() */
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d ctest1"
+ " Ia 1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2" " Ib 2\n"
+ /* second e_info() */
+ "UPDATE %"PRIu64" 0 0 0"
+ " s"__FILE__" %d ctest1"
+ " Ia 1000\n"
+ "BEGIN %"PRIu64" %"PRIu64" 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest2 Ib 2\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest3"
+ " Ic 3\n"
+ "END\t%"PRIu64"\n"
+ "END\t%"PRIu64"\n",
+ id, line, /* BEGIN */
+ id, line_log1, /* EVENT */
+ id, line, /* UPDATE */
+ id + 1, id, line, /* BEGIN */
+ id + 1, line_log2, /* EVENT */
+ id + 1 /* END */,
+ id /* END */));
+
+ test_end();
+}
+
+static void test_large_event_id(void)
+{
+ TST_BEGIN("large event id");
+ int line, line_log1, line_log2, line_log3;
+ struct event *a, *b;
+ uint64_t id;
+
+ a = make_event(NULL, &test_cats[0], &line, &id);
+ a->id += 1000000;
+ id = a->id;
+ a->change_id++;
+ b = make_event(a, &test_cats[1], NULL, NULL);
+
+ ioloop_timeval.tv_sec++;
+ e_info(a, "emit");
+ line_log1 = __LINE__-1;
+ ioloop_timeval.tv_sec++;
+ e_info(b, "emit");
+ line_log2 = __LINE__-1;
+ event_add_int(a, "test1", 1);
+ e_info(b, "emit");
+ line_log3 = __LINE__-1;
+
+ event_unref(&b);
+ event_unref(&a);
+
+ test_assert(
+ compare_test_stats_to(
+ /* first e_info() */
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest1\n"
+ "BEGIN %"PRIu64" 0 1 0 0"
+ " s"__FILE__" %d"
+ " l0 0 ctest1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "UPDATE %"PRIu64" 0 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest1 Itest1 1\n"
+ "EVENT 0 %"PRIu64" 1 1 0"
+ " s"__FILE__" %d"
+ " l1 0 ctest2\n"
+ "END %"PRIu64"\n",
+ (uint64_t)0, line_log1,
+ id, line,
+ id, line_log2,
+ id, line,
+ id, line_log3,
+ id
+ )
+ );
+
+ test_end();
+}
+
+static void test_global_event(void)
+{
+ TST_BEGIN("merge events global");
+ struct event *merge_ev1 = event_create(NULL);
+ event_add_category(merge_ev1, &test_cats[0]);
+ event_add_str(merge_ev1,test_fields[0].key, test_fields[0].value.str);
+ struct event *merge_ev2 = event_create(merge_ev1);
+ event_add_int(merge_ev2,test_fields[1].key, test_fields[1].value.intmax);
+
+ struct event *global_event = event_create(NULL);
+ int global_event_line = __LINE__ - 1;
+ uint64_t global_event_id = global_event->id;
+ event_add_str(global_event, "global", "value");
+ event_push_global(global_event);
+
+ struct timeval tv;
+ event_get_create_time(merge_ev1, &tv);
+
+ e_info(merge_ev2, "info message");
+ int log_line = __LINE__ - 1;
+
+ event_pop_global(global_event);
+ event_unref(&merge_ev1);
+ event_unref(&merge_ev2);
+ event_unref(&global_event);
+
+ test_assert(
+ compare_test_stats_to(
+ "BEGIN\t%"PRIu64"\t0\t1\t0\t0"
+ "\ts"__FILE__"\t%d"
+ "\tSglobal\tvalue\n"
+ "EVENT\t%"PRIu64"\t0\t1\t0\t0"
+ "\ts"__FILE__"\t%d\tl0\t0"
+ "\tctest1\tIkey2\t20\tSkey1\tstr1\n"
+ "END\t%"PRIu64"\n",
+ global_event_id, global_event_line,
+ global_event_id, log_line,
+ global_event_id));
+ test_end();
+}
+
+static int run_tests(void)
+{
+ int ret;
+ void (*const tests[])(void) = {
+ test_no_merging1,
+ test_no_merging2,
+ test_no_merging3,
+ test_merge_events1,
+ test_merge_events2,
+ test_skip_parents,
+ test_merge_events_skip_parents,
+ test_parent_update_post_send,
+ test_large_event_id,
+ test_global_event,
+ NULL
+ };
+ struct ioloop *ioloop = io_loop_create();
+ struct stats_client *stats_client = stats_client_init(SOCK_FULL, FALSE);
+ register_all_categories();
+ wait_for_signal(stats_ready);
+ /* Remove stats data file containing register categories related stuff */
+ i_unlink(stats_data_file);
+ ret = test_run(tests);
+ stats_client_deinit(&stats_client);
+ signal_process(exit_stats);
+ signal_process(test_done);
+ (void)waitpid(stats_pid, NULL, 0);
+ io_loop_destroy(&ioloop);
+ return ret;
+}
+
+static void cleanup_test_stats(void)
+{
+ i_unlink_if_exists(SOCK_FULL);
+ i_unlink_if_exists(stats_data_file);
+ i_unlink_if_exists(test_done);
+ i_unlink_if_exists(exit_stats);
+ i_unlink_if_exists(stats_ready);
+}
+
+static int launch_test_stats(void)
+{
+ int ret;
+
+ /* Make sure files are not existing */
+ cleanup_test_stats();
+
+ if ((stats_pid = fork()) == (pid_t)-1)
+ i_fatal("fork() failed: %m");
+ if (stats_pid == 0) {
+ stats_proc();
+ return 0;
+ }
+ wait_for_signal(stats_ready);
+ ret = run_tests();
+
+ /* Make sure we don't leave anything behind */
+ cleanup_test_stats();
+
+ return ret;
+}
+
+int main(void)
+{
+ int ret;
+ i_set_info_handler(test_fail_callback);
+ lib_init();
+ ret = launch_test_stats();
+ lib_deinit();
+ return ret;
+}