Skip to content
Snippets Groups Projects
Commit 5e890b6a authored by Andrey Saranchin's avatar Andrey Saranchin Committed by Vladimir Davydov
Browse files

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
parent ef9e3320
No related branches found
No related tags found
No related merge requests found
......@@ -46,6 +46,7 @@ set(core_sources
cord_on_demand.cc
tweaks.c
tt_sort.c
event.c
)
if (ENABLE_BACKTRACE)
......
/*
* 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;
}
/*
* 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) */
......@@ -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
......
......@@ -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;
......
......@@ -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
)
#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;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment