From 5e890b6a1f3a25ca562346f435e1697db9a81891 Mon Sep 17 00:00:00 2001 From: Andrey Saranchin <Andrey22102001@gmail.com> Date: Wed, 28 Jun 2023 12:47:01 +0300 Subject: [PATCH] core: introduce event subsystem The patch introduces new event subsystem. This subsystem is designed to store user-defined triggers and has nothing in common with core triggers. Each trigger has its own name and is represented by func_adapter. Triggers are stored in events - named wrappers over rlist. Event objects are opaque, hence rlist field should not be used directly - event provides event_find_trigger, event_reset_triggers methods and event_trigger_iterator. Iterator provides stable iteration and all the non-deleted triggers will surely be traversed. On way to the goal this patch also fixes include list in func_adapter.h. Part of #8656 NO_CHANGELOG=internal NO_DOC=internal --- src/lib/core/CMakeLists.txt | 1 + src/lib/core/event.c | 337 +++++++++++++++++++++++++++ src/lib/core/event.h | 162 +++++++++++++ src/lib/core/func_adapter.h | 3 + src/main.cc | 3 + test/unit/CMakeLists.txt | 5 + test/unit/event.c | 445 ++++++++++++++++++++++++++++++++++++ 7 files changed, 956 insertions(+) create mode 100644 src/lib/core/event.c create mode 100644 src/lib/core/event.h create mode 100644 test/unit/event.c diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt index 4f578e4c86..15803d9f58 100644 --- a/src/lib/core/CMakeLists.txt +++ b/src/lib/core/CMakeLists.txt @@ -46,6 +46,7 @@ set(core_sources cord_on_demand.cc tweaks.c tt_sort.c + event.c ) if (ENABLE_BACKTRACE) diff --git a/src/lib/core/event.c b/src/lib/core/event.c new file mode 100644 index 0000000000..1d97701388 --- /dev/null +++ b/src/lib/core/event.c @@ -0,0 +1,337 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2023, Tarantool AUTHORS, please see AUTHORS file. + */ +#include "event.h" + +#include <assert.h> + +#include "assoc.h" +#include "diag.h" +#include "trivia/util.h" +#include "func_adapter.h" + +/** Registry of all events: name -> event. */ +static struct mh_strnptr_t *event_registry; + +/** + * A named node of the list, containing func_adapter. Every event_trigger is + * associated with an event. Since event_triggers have completely different + * call interface, they are not inherited from core triggers. + */ +struct event_trigger { + /** A link in a list of all registered event triggers. */ + struct rlist link; + /** Trigger function. */ + struct func_adapter *func; + /** Backlink to event. Needed for reference count of event. */ + struct event *event; + /** Unique name of the trigger. */ + char *name; + /** Trigger reference counter. */ + uint32_t ref_count; + /** This flag is set when the trigger is deleted. */ + bool is_deleted; +}; + +/** + * Creates event_trigger from func_adapter and name and binds it to passed + * event. Created trigger owns the adapter and will destroy it in its + * destructor. Passed name must be zero terminated and will be copied. + * Passed event must not be NULL and will be referenced by created trigger. + * Note that the function does not increment trigger_count field of the event. + */ +static struct event_trigger * +event_trigger_new(struct func_adapter *func, struct event *event, + const char *name) +{ + assert(name != NULL); + assert(event != NULL); + size_t name_len = strlen(name); + struct event_trigger *trigger = + xmalloc(sizeof(*trigger) + name_len + 1); + trigger->func = func; + trigger->name = (char *)(trigger + 1); + strlcpy(trigger->name, name, name_len + 1); + trigger->ref_count = 0; + trigger->is_deleted = false; + trigger->event = event; + event_ref(event); + rlist_create(&trigger->link); + return trigger; +} + +/** + * Destroys an event_trigger. Underlying func_adapter is destroyed, + * event_trigger is deallocated. + */ +static void +event_trigger_delete(struct event_trigger *trigger) +{ + assert(trigger->func != NULL); + assert(trigger->event != NULL); + rlist_del(&trigger->link); + event_unref(trigger->event); + func_adapter_destroy(trigger->func); + TRASH(trigger); + free(trigger); +} + +/** + * Increments event_trigger reference counter. + */ +static void +event_trigger_ref(struct event_trigger *trigger) +{ + trigger->ref_count++; +} + +/** + * Decrements event_trigger reference counter. The event_trigger is destroyed + * if the counter reaches zero. + */ +static void +event_trigger_unref(struct event_trigger *trigger) +{ + assert(trigger != NULL); + assert(trigger->ref_count > 0); + trigger->ref_count--; + assert(trigger->ref_count > 0 || trigger->is_deleted); + if (trigger->ref_count == 0) + event_trigger_delete(trigger); +} + +/** + * Allocates, initializes a new event and inserts it to event registry. + * Passed name will be copied. + */ +static struct event * +event_new(const char *name, size_t name_len) +{ + assert(name != NULL); + assert(name_len > 0); + struct event *event = xmalloc(sizeof(*event) + name_len + 1); + rlist_create(&event->triggers); + event->ref_count = 0; + event->trigger_count = 0; + event->name = (char *)(event + 1); + strlcpy(event->name, name, name_len + 1); + + struct mh_strnptr_t *h = event_registry; + name = event->name; + uint32_t name_hash = mh_strn_hash(name, name_len); + struct mh_strnptr_node_t node = {name, name_len, name_hash, event}; + struct mh_strnptr_node_t prev; + struct mh_strnptr_node_t *prev_ptr = &prev; + mh_int_t pos = mh_strnptr_put(h, &node, &prev_ptr, NULL); + (void)pos; + assert(pos != mh_end(h)); + assert(prev_ptr == NULL); + + return event; +} + +/** + * Destroys an event and removes it from event registry. + * Underlying trigger list is destroyed. + */ +void +event_delete(struct event *event) +{ + assert(event != NULL); + + struct mh_strnptr_t *h = event_registry; + const char *name = event->name; + size_t name_len = strlen(name); + mh_int_t i = mh_strnptr_find_str(h, name, name_len); + assert(i != mh_end(h)); + assert(mh_strnptr_node(h, i)->val == event); + mh_strnptr_del(h, i, 0); + TRASH(event); + free(event); +} + +/** + * Finds internal event_trigger entity by name in the event. + * All arguments must not be NULL. + */ +static struct event_trigger * +event_find_trigger_internal(struct event *event, const char *name) +{ + struct event_trigger *curr = NULL; + rlist_foreach_entry(curr, &event->triggers, link) { + if (!curr->is_deleted && strcmp(curr->name, name) == 0) + return curr; + } + return NULL; +} + +struct func_adapter * +event_find_trigger(struct event *event, const char *name) +{ + struct event_trigger *trigger = + event_find_trigger_internal(event, name); + return trigger != NULL ? trigger->func : NULL; +} + +void +event_reset_trigger(struct event *event, const char *name, + struct func_adapter *new_trigger) +{ + assert(event != NULL); + assert(name != NULL); + struct event_trigger *found_trigger = + event_find_trigger_internal(event, name); + if (new_trigger != NULL) { + event->trigger_count++; + struct event_trigger *trigger = + event_trigger_new(new_trigger, event, name); + event_trigger_ref(trigger); + if (found_trigger == NULL) { + rlist_add_entry(&event->triggers, trigger, link); + } else { + /* + * Insert new trigger before the replaced one not to + * iterate over both of them in the case when an + * iterator points to a replaced trigger. + */ + rlist_add_tail_entry(&found_trigger->link, + trigger, link); + } + } + if (found_trigger != NULL) { + assert(event->trigger_count > 0); + event->trigger_count--; + found_trigger->is_deleted = true; + event_trigger_unref(found_trigger); + } +} + +void +event_trigger_iterator_create(struct event_trigger_iterator *it, + struct event *event) +{ + event_ref(event); + it->event = event; + it->curr = &event->triggers; +} + +bool +event_trigger_iterator_next(struct event_trigger_iterator *it, + struct func_adapter **func, const char **name) +{ + assert(func != NULL); + assert(name != NULL); + + *func = NULL; + *name = NULL; + /* Iterator is exhausted - return. */ + if (it->curr == NULL) + return false; + struct event_trigger *trigger; + struct rlist *old = it->curr; + /* We need to skip all the deleted triggers. */ + do { + it->curr = rlist_next(it->curr); + trigger = rlist_entry(it->curr, struct event_trigger, link); + /* We have traversed the whole list. */ + if (it->curr == &it->event->triggers) { + it->curr = NULL; + trigger = NULL; + goto release; + } + } while (trigger->is_deleted); + assert(trigger != NULL); + event_trigger_ref(trigger); +release: + if (old != NULL && old != &it->event->triggers) { + struct event_trigger *old_trigger = + rlist_entry(old, struct event_trigger, link); + assert(old_trigger != trigger); + event_trigger_unref(old_trigger); + } + if (trigger != NULL) { + *func = trigger->func; + *name = trigger->name; + return true; + } + return false; +} + +void +event_trigger_iterator_destroy(struct event_trigger_iterator *it) +{ + if (it->curr != NULL && it->curr != &it->event->triggers) { + struct event_trigger *curr_trigger = + rlist_entry(it->curr, struct event_trigger, link); + event_trigger_unref(curr_trigger); + } + event_unref(it->event); + it->event = NULL; + it->curr = NULL; +} + +struct event * +event_get(const char *name, bool create_if_not_exist) +{ + assert(event_registry != NULL); + assert(name != NULL); + struct mh_strnptr_t *h = event_registry; + uint32_t name_len = strlen(name); + mh_int_t i = mh_strnptr_find_str(h, name, name_len); + if (i != mh_end(h)) + return mh_strnptr_node(h, i)->val; + else if (!create_if_not_exist) + return NULL; + + struct event *event = event_new(name, name_len); + return event; +} + +bool +event_foreach(event_foreach_f cb, void *arg) +{ + struct mh_strnptr_t *h = event_registry; + mh_int_t i; + mh_foreach(h, i) { + struct mh_strnptr_node_t *node = mh_strnptr_node(h, i); + struct event *event = node->val; + if (!event_has_triggers(event)) + continue; + if (!cb(event, arg)) + return false; + } + return true; +} + +void +event_init(void) +{ + event_registry = mh_strnptr_new(); +} + +void +event_free(void) +{ + assert(event_registry != NULL); + struct mh_strnptr_t *h = event_registry; + mh_int_t i; + mh_foreach(h, i) { + struct event *event = mh_strnptr_node(h, i)->val; + /* + * If the only thing that holds the event is its trigger list, + * the reference counter will reach zero when the list will be + * cleared and the destructor will be called. Since we will + * call destructor manually, let's reference the event in order + * to prevent such situation. + */ + event_ref(event); + struct event_trigger *trigger, *tmp; + rlist_foreach_entry_safe(trigger, &event->triggers, link, tmp) + event_trigger_delete(trigger); + event_delete(event); + } + mh_strnptr_delete(h); + event_registry = NULL; +} diff --git a/src/lib/core/event.h b/src/lib/core/event.h new file mode 100644 index 0000000000..098c64d9da --- /dev/null +++ b/src/lib/core/event.h @@ -0,0 +1,162 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright 2010-2023, Tarantool AUTHORS, please see AUTHORS file. + */ +#pragma once + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> + +#include "small/rlist.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct func_adapter; + +/** + * List of triggers registered on event identified by name. + */ +struct event { + /** List of triggers. */ + struct rlist triggers; + /** Name of event. */ + char *name; + /** Reference count. */ + uint32_t ref_count; + /** Number of triggers. */ + uint32_t trigger_count; +}; + +/** + * Destroys an event and removes it from event registry. + * NB: The method is private and must not be called manually. + */ +void +event_delete(struct event *event); + +/** + * Increments event reference counter. + */ +static inline void +event_ref(struct event *event) +{ + event->ref_count++; +} + +/** + * Decrements event reference counter. The event is destroyed if the counter + * reaches zero. + */ +static inline void +event_unref(struct event *event) +{ + assert(event->ref_count > 0); + event->ref_count--; + if (event->ref_count == 0) { + assert(rlist_empty(&event->triggers)); + assert(event->trigger_count == 0); + event_delete(event); + } +} + +/** + * Checks if the event has no triggers. + */ +static inline bool +event_has_triggers(struct event *event) +{ + return event->trigger_count > 0; +} + +/** + * Finds a trigger by name in an event. All the arguments must not be NULL. + */ +struct func_adapter * +event_find_trigger(struct event *event, const char *name); + +/** + * Resets trigger by name in an event. + * Arguments event and name must not be NULL. + * If new_trigger is NULL, the function removes a trigger by name from the + * event. Otherwise, it replaces trigger by name or inserts it in the beginning + * of the underlying list of event. + */ +void +event_reset_trigger(struct event *event, const char *name, + struct func_adapter *new_trigger); + +/** + * Iterator over triggers from event. Never invalidates. + */ +struct event_trigger_iterator { + /** + * Current element in the list of triggers. + * Becomes NULL when the iterator is exhausted. + */ + struct rlist *curr; + /** Underlying event. */ + struct event *event; +}; + +/** + * Initializes iterator. + */ +void +event_trigger_iterator_create(struct event_trigger_iterator *it, + struct event *event); + +/** + * Advances iterator. Output arguments trigger and name must not be NULL. + */ +bool +event_trigger_iterator_next(struct event_trigger_iterator *it, + struct func_adapter **trigger, const char **name); + +/** + * Deinitializes iterator. Does not free memory. + */ +void +event_trigger_iterator_destroy(struct event_trigger_iterator *it); + +/** + * Finds an event by its name. Name must be a zero-terminated string. + * Creates new event and inserts it to registry if there is no event with such + * name when flag create_if_not_exist is true. + */ +struct event * +event_get(const char *name, bool create_if_not_exist); + +typedef bool +event_foreach_f(struct event *event, void *arg); + +/** + * Invokes a callback for each registered event with no particular order. + * + * The callback is passed an event object and the given argument. + * If it returns true, iteration continues. Otherwise, iteration breaks, and + * the function returns false. + * + * Empty events are guaranteed to be skipped. + */ +bool +event_foreach(event_foreach_f cb, void *arg); + +/** + * Initializes event submodule. + */ +void +event_init(void); + +/** + * Frees event submodule. + */ +void +event_free(void); + +#if defined(__cplusplus) +} /* extern "C" */ +#endif /* defined(__cplusplus) */ diff --git a/src/lib/core/func_adapter.h b/src/lib/core/func_adapter.h index 29490d9627..8ace5cefed 100644 --- a/src/lib/core/func_adapter.h +++ b/src/lib/core/func_adapter.h @@ -5,10 +5,13 @@ */ #pragma once +#include <assert.h> #include <stdbool.h> #include <stdint.h> #include <string.h> +#include "trivia/util.h" + #ifdef __cplusplus extern "C" { #endif diff --git a/src/main.cc b/src/main.cc index 7aca7658a5..ca72f3e356 100644 --- a/src/main.cc +++ b/src/main.cc @@ -88,6 +88,7 @@ #include "core/errinj.h" #include "core/clock_lowres.h" #include "lua/utils.h" +#include "core/event.h" static pid_t master_pid = getpid(); static struct pidfh *pid_file_handle; @@ -592,6 +593,7 @@ tarantool_free(void) session_free(); user_cache_free(); #endif + event_free(); ssl_free(); memtx_tx_manager_free(); coll_free(); @@ -875,6 +877,7 @@ main(int argc, char **argv) memtx_tx_manager_init(); module_init(); ssl_init(); + event_init(); systemd_init(); const int override_cert_paths_env_vars = 0; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c18ef408e1..4c9729026b 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -630,3 +630,8 @@ create_unit_test(PREFIX lua_func_adapter ${ICU_LIBRARIES} ${LUAJIT_LIBRARIES} ) + +create_unit_test(PREFIX event + SOURCES event.c core_test_utils.c + LIBRARIES core unit +) diff --git a/test/unit/event.c b/test/unit/event.c new file mode 100644 index 0000000000..d5aba3e6a6 --- /dev/null +++ b/test/unit/event.c @@ -0,0 +1,445 @@ +#include <stdbool.h> +#include <stddef.h> +#include <string.h> + +#include "diag.h" +#include "error.h" +#include "errcode.h" +#include "fiber.h" +#include "func_adapter.h" +#include "memory.h" +#include "trivia/util.h" +#include "event.h" +#include "tt_static.h" + +#define UNIT_TAP_COMPATIBLE 1 +#include "unit.h" + +static int func_destroy_count; + +/** + * Virtual destructor for func_adapter for the test purposes. + */ +void +func_destroy(struct func_adapter *func) +{ + func_destroy_count++; +} + +/** + * The test creates event with different names and check if all the basic + * operations works correctly. + */ +static void +test_basic(void) +{ + plan(4 * 14); + + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + const char *trg_name = "my_triggers.trg[1]"; + const char *names[] = { + "name", + "name with spaces", + "namespace.name", + "NAMESPACE[123].name" + }; + for (size_t i = 0; i < lengthof(names); ++i) { + const char *name = names[i]; + struct event *event = event_get(name, false); + is(event, NULL, "No such event - NULL must be returned"); + event = event_get(name, true); + isnt(event, NULL, "Event must be created"); + /* Reference event to prevent deletion when it'll be empty. */ + event_ref(event); + struct event *found_event = event_get(name, false); + is(found_event, event, "Existing event must be found"); + struct func_adapter *old = event_find_trigger(event, trg_name); + is(old, NULL, "No such trigger - NULL must be returned"); + ok(!event_has_triggers(event), "Created event must be empty"); + event_reset_trigger(event, trg_name, &func); + found_event = event_get(name, false); + is(found_event, event, "Event must still exist"); + old = event_find_trigger(event, trg_name); + is(old, &func, "New trigger must be found"); + ok(event_has_triggers(event), "Event must not be empty"); + is(func_destroy_count, 0, "Func must not be destroyed yet") + event_reset_trigger(event, trg_name, NULL); + is(func_destroy_count, 1, "Func must be destroyed") + old = event_find_trigger(event, trg_name); + is(old, NULL, "Deleted trigger must not be found"); + ok(!event_has_triggers(event), "Event must be empty"); + found_event = event_get(name, false); + is(found_event, event, "Referenced event must not be deleted"); + event_unref(event); + found_event = event_get(name, false); + is(found_event, NULL, "Empty unused event must be deleted"); + func_destroy_count = 0; + } + check_plan(); +} + +/** + * A test argument for event_foreach function. + */ +struct test_event_foreach_arg { + int names_len; + const char **names; + int traversed; +}; + +static bool +test_event_foreach_f(struct event *event, void *arg) +{ + struct test_event_foreach_arg *data = + (struct test_event_foreach_arg *)arg; + data->traversed++; + bool name_found = false; + for (int i = 0; i < data->names_len && !name_found; ++i) { + name_found = strcmp(event->name, data->names[i]) == 0; + } + ok(name_found, "Traversed event must really exist"); + return true; +} + +static bool +test_event_foreach_return_false_f(struct event *event, void *arg) +{ + (void)event; + struct test_event_foreach_arg *data = + (struct test_event_foreach_arg *)arg; + data->traversed++; + return false; +} + +static void +test_event_foreach(void) +{ + plan(10); + const char *names[] = { + "event", + "my_events.event1", + "my_events.event3", + "my_events[15].event" + }; + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + for (size_t i = 0; i < lengthof(names); ++i) { + struct event *event = event_get(names[i], true); + event_ref(event); + const char *trg_name = tt_sprintf("%zu", i); + event_reset_trigger(event, trg_name, &func); + } + + struct test_event_foreach_arg arg = { + .names_len = lengthof(names), + .names = names, + .traversed = 0, + }; + + bool ok = event_foreach(test_event_foreach_f, &arg); + ok(ok, "Traversal must return true"); + is(arg.traversed, lengthof(names), "All the events must be traversed"); + + arg.traversed = 0; + ok = event_foreach(test_event_foreach_return_false_f, &arg); + ok(!ok, "Failed traversal must return false"); + is(arg.traversed, 1, "Only one event must be traversed"); + + for (size_t i = 0; i < lengthof(names); ++i) { + struct event *event = event_get(names[i], false); + const char *trg_name = tt_sprintf("%zu", i); + event_reset_trigger(event, trg_name, NULL); + } + + arg.traversed = 0; + ok = event_foreach(test_event_foreach_f, &arg); + ok(ok, "Traversal of empty registry must return true"); + is(arg.traversed, 0, "All the events are empty - nothing to traverse"); + + /* Unreference all the events. */ + for (size_t i = 0; i < lengthof(names); ++i) { + struct event *event = event_get(names[i], false); + fail_if(event == NULL); + event_unref(event); + } + + check_plan(); +} + +static void +test_event_trigger_iterator(void) +{ + plan(14); + const char *event_name = "test_event"; + const char *trigger_names[] = { + "0", "1", "2", "3", "4", "5", "6", "7" + }; + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + + struct event *event = event_get(event_name, true); + for (int i = lengthof(trigger_names) - 1; i >= 0; --i) + event_reset_trigger(event, trigger_names[i], &func); + + struct event_trigger_iterator it; + event_trigger_iterator_create(&it, event); + size_t idx = 0; + struct func_adapter *curr_trg = NULL; + const char *curr_name = NULL; + while (event_trigger_iterator_next(&it, &curr_trg, &curr_name)) { + is(strcmp(curr_name, trigger_names[idx]), 0, + "Triggers must be traversed in reversed order"); + idx++; + } + is(idx, lengthof(trigger_names), "All the triggers must be traversed"); + is(curr_trg, NULL, "Exhausted iterator must return NULL trigger"); + is(curr_name, NULL, "Exhausted iterator must return NULL name"); + + curr_trg = &func; + curr_name = "garbage"; + bool has_elems = + event_trigger_iterator_next(&it, &curr_trg, &curr_name); + ok(!has_elems, "Iterator must stay exhausted"); + is(curr_trg, NULL, "Exhausted iterator must return NULL trigger"); + is(curr_name, NULL, "Exhausted iterator must return NULL name"); + + for (size_t i = 0; i < lengthof(trigger_names); ++i) + event_reset_trigger(event, trigger_names[i], NULL); + + event_trigger_iterator_destroy(&it); + + check_plan(); +} + +/** + * Stops at breakpoint and deletes triggers which are set in del mask. + */ +static void +test_event_iterator_stability_del_step(int breakpoint, const char *del_mask, + int trigger_num) +{ + fail_unless(breakpoint < trigger_num); + size_t left_after_br = 0; + for (int i = breakpoint + 1; i < trigger_num; ++i) { + if (del_mask[i] == 0) + left_after_br++; + } + plan((breakpoint + 1) * 2 + left_after_br + 3); + + const char *event_name = "test_event"; + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + + struct event *event = event_get(event_name, true); + /* + * Reference the event to prevent deletion for the test cases that + * delete all the triggers from the event. + */ + event_ref(event); + for (int i = trigger_num - 1; i >= 0; --i) { + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, &func); + } + + struct event_trigger_iterator it; + event_trigger_iterator_create(&it, event); + struct func_adapter *trg = NULL; + const char *name = NULL; + for (int i = 0; i <= breakpoint; i++) { + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(ok, "Iterator must not be exhausted yet") + const char *trg_name = tt_sprintf("%d", i); + is(strcmp(name, trg_name), 0, + "Triggers must be traversed in reversed order"); + } + bool delete_all_triggers = true; + for (int i = 0; i < trigger_num; ++i) { + if (del_mask[i] != 0) { + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, NULL); + } else { + delete_all_triggers = false; + } + } + is(event_has_triggers(event), !delete_all_triggers, + "Function event_has_triggers must work correctly"); + for (size_t i = 0; i < left_after_br; ++i) { + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(ok, "Traversal must continue"); + } + + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(!ok, "Iterator must be exhausted"); + event_trigger_iterator_destroy(&it); + is(event_has_triggers(event), !delete_all_triggers, + "Function event_has_triggers must work correctly"); + + for (int i = 0; i < trigger_num; ++i) { + if (del_mask[i] != 0) + continue; + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, NULL); + } + event_unref(event); + + check_plan(); +} + +/** + * Stops at breakpoint and replaces triggers which are set in replace mask. + */ +static void +test_event_iterator_stability_replace_step(int breakpoint, + const char *replace_mask, + int trigger_num) +{ + fail_unless(breakpoint < trigger_num); + plan((breakpoint + 1) * 2 + 3 * (trigger_num - breakpoint - 1) + 3); + + const char *event_name = "test_event"; + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + struct func_adapter new_func = {.vtab = &vtab}; + + struct event *event = event_get(event_name, true); + for (int i = trigger_num - 1; i >= 0; --i) { + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, &func); + } + + struct event_trigger_iterator it; + event_trigger_iterator_create(&it, event); + struct func_adapter *trg = NULL; + const char *name = NULL; + for (int i = 0; i <= breakpoint; i++) { + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(ok, "Iterator must not be exhausted yet") + const char *trg_name = tt_sprintf("%d", i); + is(strcmp(name, trg_name), 0, + "Triggers must be traversed in reversed order"); + } + for (int i = 0; i < trigger_num; ++i) { + if (replace_mask[i] != 0) { + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, &new_func); + } + } + ok(event_has_triggers(event), "Event must not be empty"); + for (int i = breakpoint + 1; i < trigger_num; ++i) { + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(ok, "Traversal must continue") + const char *trg_name = tt_sprintf("%d", i); + is(strcmp(name, trg_name), 0, + "Triggers must be traversed in reversed order"); + if (replace_mask[i] != 0) { + is(trg, &new_func, "Trigger must be replaced"); + } else { + is(trg, &func, "Trigger must be old"); + } + } + bool ok = event_trigger_iterator_next(&it, &trg, &name); + ok(!ok, "Iterator must be exhausted"); + event_trigger_iterator_destroy(&it); + ok(event_has_triggers(event), "Event must not be empty"); + + for (int i = 0; i < trigger_num; ++i) { + const char *trg_name = tt_sprintf("%d", i); + event_reset_trigger(event, trg_name, NULL); + } + + check_plan(); +} + +/** + * Checks if iteration is stable in the cases of deletions and replaces. + */ +static void +test_event_trigger_iterator_stability(void) +{ + plan(6); + char mask[8]; + const size_t trigger_num = lengthof(mask); + memset(mask, 0, trigger_num); + size_t br = trigger_num / 2; + /** + * Delete or replace current trigger. + */ + mask[br] = 1; + test_event_iterator_stability_del_step(br, mask, trigger_num); + test_event_iterator_stability_replace_step(br, mask, trigger_num); + memset(mask, 0, trigger_num); + /** + * Delete or replace current, previous and next triggers. + */ + mask[br - 1] = 1; + mask[br] = 1; + mask[br + 1] = 1; + test_event_iterator_stability_del_step(br, mask, trigger_num); + test_event_iterator_stability_replace_step(br, mask, trigger_num); + memset(mask, 0, trigger_num); + /** + * Delete or replace all the triggers in the middle of iteration. + */ + memset(mask, 1, trigger_num); + test_event_iterator_stability_del_step(br, mask, trigger_num); + test_event_iterator_stability_replace_step(br, mask, trigger_num); + memset(mask, 0, trigger_num); + + check_plan(); +} + +static void +test_event_free(void) +{ + plan(1); + + struct func_adapter_vtab vtab = {.destroy = func_destroy}; + struct func_adapter func = {.vtab = &vtab}; + const char *trigger_names[] = { + "trigger[1]", + "trigger.second", + "another_trigger" + }; + const char *event_names[] = { + "name", + "name with spaces", + "namespace.name", + "NAMESPACE[123].name" + }; + func_destroy_count = 0; + for (size_t i = 0; i < lengthof(event_names); ++i) { + const char *name = event_names[i]; + struct event *event = event_get(name, true); + for (size_t j = 0; j < lengthof(trigger_names); ++j) + event_reset_trigger(event, trigger_names[j], &func); + } + event_free(); + is(func_destroy_count, lengthof(event_names) * lengthof(trigger_names), + "All triggers must be destroyed"); + /* Initialize event back. */ + event_init(); + + check_plan(); +} + +static int +test_main(void) +{ + plan(5); + test_basic(); + test_event_foreach(); + test_event_trigger_iterator(); + test_event_trigger_iterator_stability(); + test_event_free(); + return check_plan(); +} + +int +main(void) +{ + event_init(); + int rc = test_main(); + event_free(); + return rc; +} -- GitLab