From edba76c4edb870df264757a6dd5e3a0626a31106 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Tue, 27 Jan 2026 13:39:42 +0300 Subject: [PATCH] ll_queue: fix pointer arithmetic (xxx=0), callback now receives struct ll_entry* - Change xxx from 1 to 0, fixing pointer arithmetic in queue_resume_timeout_cb - Update comments: callback receives struct ll_entry* not user data - Rename payload field to data in struct ll_entry - etcp: fix INFLIGHT_PACKET to ACK_PACKET type in etcp_conn_input - debug: remove excessive DEBUG_ERROR/DEBUG_DEBUG messages - tests: rewrite test_ll_queue.c for new architecture --- lib/ll_queue.c | 4 +- lib/ll_queue.h | 16 +- lib/u_async.c | 16 +- src/etcp.c | 22 +- src/etcp_connections.c | 15 +- src/etcp_loadbalancer.c | 4 - src/utun_instance.c | 6 - tests/Makefile.am | 2 +- tests/test_ll_queue.c | 1254 +++++++-------------------------------- 9 files changed, 242 insertions(+), 1097 deletions(-) mode change 100644 => 100755 lib/ll_queue.c diff --git a/lib/ll_queue.c b/lib/ll_queue.c old mode 100644 new mode 100755 index 6a12c584..abb87fef --- a/lib/ll_queue.c +++ b/lib/ll_queue.c @@ -13,7 +13,7 @@ static void check_waiters(struct ll_queue* q); static void add_to_hash(struct ll_queue* q, struct ll_entry* entry); static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry); -#define xxx 1 +#define xxx 0 // Вспомогательная функция для преобразования данных в запись static inline struct ll_entry* data_to_entry(void* data) { @@ -86,7 +86,7 @@ static void queue_resume_timeout_cb(void* arg) { // Вызвать коллбэк если есть элементы и коллбэки разрешены if (q->head && !q->callback_suspended && q->callback) { - void* data = (void*)(q->head + 1); + void* data = (void*)(q->head + xxx); q->callback(q, data, q->callback_arg); } } diff --git a/lib/ll_queue.h b/lib/ll_queue.h index 0b9bec7b..638d2472 100644 --- a/lib/ll_queue.h +++ b/lib/ll_queue.h @@ -14,7 +14,7 @@ struct memory_pool; struct UASYNC; // Автозабор элемента из очереди. вызывается когда в очереди что-то есть и коллбэк не занят обработкой. -// Параметры: указатель на очередь, указатель на пользовательские данные, пользовательский аргумент +// Параметры: указатель на очередь, указатель на структуру элемента (struct ll_entry*), пользовательский аргумент // Когда коллбэк закончит обрабатывать элемент он должен вызвать queue_resume_callback - сообщить о готовности получить следующий элемент (приём очередным вызовом коллбэка в следующем цикле mainloop). typedef void (*queue_callback_fn)(struct ll_queue* q, void* data, void* arg); @@ -27,7 +27,7 @@ struct ll_entry { uint32_t id; // Идентификатор для хеш-поиска struct ll_entry* hash_next; // Следующий в хеш-цепочке struct memory_pool* pool; // Пул, из которого выделен этот элемент (NULL, если выделен через malloc) - uint8_t payload[0]; + uint8_t data[0]; }; // Ожидающий добавления элементов в очередь (пока очередь не освободться до нужного размера чтобы не забивать) @@ -96,12 +96,12 @@ void queue_set_size_limit(struct ll_queue* q, int lim); // Создать новый элемент с областью данных указанного размера // Память выделяется одним блоком: [struct ll_entry][область данных data_size байт] -// Возвращает: указатель на пользовательские данные или NULL при ошибке выделения памяти +// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL при ошибке выделения памяти // ПРИМЕЧАНИЕ: ref_count = 1, элемент не в очереди void* queue_data_new(size_t data_size); // Создать новый элемент из пула (размер был определен при создании пула) -// Возвращает: указатель на пользовательские данные или NULL при ошибке выделения памяти +// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL при ошибке выделения памяти // ПРИМЕЧАНИЕ: ref_count = 1, элемент не в очереди void* queue_data_new_from_pool(struct memory_pool* pool); @@ -125,7 +125,7 @@ int queue_data_put_first(struct ll_queue* q, void* data, uint32_t id); // Извлечь элемент из начала очереди // При извлечении приостанавливает коллбэки (callback_suspended = 1) чтобы предотвратить рекурсию -// Возвращает: указатель на пользовательские данные или NULL если очередь пуста +// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL если очередь пуста // ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента, просто удаляет из очереди void* queue_data_get(struct ll_queue* q); @@ -155,12 +155,12 @@ static inline size_t queue_total_bytes(struct ll_queue* q) { // ==================== Поиск и удаление по ID ==================== // Найти элемент по ID -// Возвращает: указатель на пользовательские данные или NULL если не найден +// Возвращает: указатель на структуру элемента (struct ll_entry*) или NULL если не найден void* queue_find_data_by_id(struct ll_queue* q, uint32_t id); -// Удалить элемент из очереди по указателю на данные +// Удалить элемент из очереди по указателю на структуру элемента // Возвращает: 0 при успехе, -1 если элемент не найден // ПРИМЕЧАНИЕ: НЕ изменяет ref_count элемента, просто удаляет из очереди int queue_remove_data(struct ll_queue* q, void* data); -#endif // LL_QUEUE_H \ No newline at end of file +#endif // LL_QUEUE_H diff --git a/lib/u_async.c b/lib/u_async.c index 575c006f..c8790463 100644 --- a/lib/u_async.c +++ b/lib/u_async.c @@ -292,8 +292,7 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c if (!ua || timeout_tb < 0 || !callback) return NULL; if (!ua->timeout_heap) return NULL; - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: ua=%p, timeout=%d tb, arg=%p, callback=%p", - ua, timeout_tb, arg, callback); + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback); struct timeout_node* node = malloc(sizeof(struct timeout_node)); if (!node) { @@ -302,9 +301,6 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c } ua->timer_alloc_count++; - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: allocated node %p (alloc_count=%zu)", - node, ua->timer_alloc_count); - node->arg = arg; node->callback = callback; node->ua = ua; @@ -316,9 +312,6 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c timeval_add_tb(&now, timeout_tb); node->expiration_ms = timeval_to_ms(&now); - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: node %p expires at %llu ms", - node, (unsigned long long)node->expiration_ms); - // Add to heap if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap"); @@ -327,7 +320,6 @@ void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_c return NULL; } - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: successfully created timer %p", node); return node; } @@ -343,9 +335,6 @@ err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { struct timeout_node* node = (struct timeout_node*)t_id; - DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: ua=%p, t_id=%p, node=%p, expires=%llu ms", - ua, t_id, node, (unsigned long long)node->expiration_ms); - // Try to cancel from heap first if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) { // Successfully removed from heap - mark as cancelled and update counter @@ -357,6 +346,9 @@ err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) { return ERR_OK; } + DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms", + ua, t_id, node, (unsigned long long)node->expiration_ms); + // If not found in heap, it may have already expired or been invalid return ERR_FAIL; } diff --git a/src/etcp.c b/src/etcp.c index 66aba1da..ac3553f7 100644 --- a/src/etcp.c +++ b/src/etcp.c @@ -63,10 +63,12 @@ static uint16_t timestamp_diff(uint16_t t1, uint16_t t2) { struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { if (!instance) return NULL; - DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: creating connection for instance %p", instance); struct ETCP_CONN* etcp = calloc(1, sizeof(struct ETCP_CONN)); - if (!etcp) return NULL; + if (!etcp) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: creating connection failed for instance %p", instance); + return NULL; + } etcp->instance = instance; etcp->input_queue = queue_new(instance->ua, 0); // No hash for input_queue @@ -78,11 +80,10 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { etcp->inflight_pool = memory_pool_init(sizeof(struct INFLIGHT_PACKET)); etcp->rx_pool = memory_pool_init(sizeof(struct ETCP_FRAGMENT)); - DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: queues created - input:%p output:%p send:%p wait:%x pool:%p", - etcp->input_queue, etcp->output_queue, etcp->input_send_q, etcp->input_wait_ack, etcp->inflight_pool); - if (!etcp->input_queue || !etcp->output_queue || !etcp->input_send_q || !etcp->recv_q || !etcp->ack_q || !etcp->input_wait_ack || !etcp->inflight_pool || !etcp->rx_pool) { + DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create: error - closing - input:%p output:%p send:%p wait:%x pool:%p", + etcp->input_queue, etcp->output_queue, etcp->input_send_q, etcp->input_wait_ack, etcp->inflight_pool); etcp_connection_close(etcp); return NULL; } @@ -100,8 +101,8 @@ struct ETCP_CONN* etcp_connection_create(struct UTUN_INSTANCE* instance) { queue_set_callback(etcp->input_send_q, input_send_q_cb, etcp); etcp->link_ready_for_send_fn = etcp_link_ready_callback; - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, mtu=%d, window_size=%u, next_tx_id=%u", - etcp->mtu, etcp->window_size, etcp->next_tx_id); + DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_connection_create: queues created, ETCP=%p mtu=%d, window_size=%u, next_tx_id=%u", + etcp, etcp->mtu, etcp->window_size, etcp->next_tx_id); return etcp; } @@ -265,7 +266,7 @@ static void input_queue_cb(struct ll_queue* q, void* arg) { // Create INFLIGHT_PACKET struct INFLIGHT_PACKET* p = memory_pool_alloc(etcp->inflight_pool); if (!p) { - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: cannot allocate INFLIGHT_PACKET"); + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "input_queue_cb: cannot allocate INFLIGHT_PACKET (pool=%p etcp=%p)", etcp->inflight_pool, etcp); queue_data_free(in_pkt); // Free the ETCP_FRAGMENT queue_resume_callback(q); return; @@ -555,10 +556,11 @@ void etcp_conn_input(struct ETCP_DGRAM* pkt) { if (len>=5) { // формируем ACK - struct INFLIGHT_PACKET* p = memory_pool_alloc(etcp->instance->ack_pool); + struct ACK_PACKET* p = memory_pool_alloc(etcp->instance->ack_pool); uint32_t seq=data[1] | (data[2]<<8) | (data[3]<<16) | (data[4]<<24); p->seq=seq; - p->last_timestamp=pkt->timestamp; + p->pkt_timestamp=pkt->timestamp; + p->recv_timestamp=get_current_timestamp(); queue_data_put(etcp->ack_q, p, p->seq); if ((int32_t)(etcp->last_delivered_id-seq)<0) if (queue_find_data_by_id(etcp->recv_q, seq)==NULL) {// проверяем есть ли пакет с этим seq diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 02535a3e..7b875aea 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -756,21 +756,16 @@ int init_connections(struct UTUN_INSTANCE* instance) { // Initialize clients - create outgoing connections struct CFG_CLIENT* client = config->clients; - DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "init_connections: START - client list = %p, first client name = %s", - client, client ? client->name : "NULL"); DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "init_connections called, instance=%p, config=%p, clients=%p, connections_count=%d", instance, config, config ? config->clients : NULL, instance ? instance->connections_count : -1); while (client) { - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Processing client: %s", client->name); - // Check if client has required configuration - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Client %s - keepalive=%d, links=%p, peer_key_len=%zu", + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Client %s - keepalive=%d, links=%p, peer_key_len=%zu", client->name, client->keepalive, client->links, strlen(client->peer_public_key_hex)); // Create ETCP connection for this client struct ETCP_CONN* etcp_conn = etcp_connection_create(instance); - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "etcp_connection_create returned: %p", etcp_conn); if (!etcp_conn) { DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Failed to create ETCP connection for client %s", client->name); @@ -778,8 +773,6 @@ int init_connections(struct UTUN_INSTANCE* instance) { continue; } - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Successfully created connection %p for client %s", etcp_conn, client->name); - // Initialize crypto context for this connection if (sc_init_ctx(&etcp_conn->crypto_ctx, &instance->my_keys) != SC_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CRYPTO, "init_connections: failed to initialize crypto context for client %s", client->name); @@ -920,15 +913,11 @@ int init_connections(struct UTUN_INSTANCE* instance) { client_link = client_link->next; } - // Add connection to instance's connection list only after all validations pass - // This prevents double-free when cleanup tries to free connections that were freed during failed initialization - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Adding connection %p to instance list", etcp_conn); - etcp_conn->next = instance->connections; instance->connections = etcp_conn; instance->connections_count++; - DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "Added connection %p to instance, total count: %d", etcp_conn, instance->connections_count); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Added connection %p to instance, total count: %d", etcp_conn, instance->connections_count); client = client->next; } diff --git a/src/etcp_loadbalancer.c b/src/etcp_loadbalancer.c index 106de1b9..89a07d00 100755 --- a/src/etcp_loadbalancer.c +++ b/src/etcp_loadbalancer.c @@ -25,8 +25,6 @@ static void shaper_timer_cb(void* arg); // Shaper wait callback // Select link for transmission struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: etcp=%p", etcp); - if (!etcp || !etcp->links) { DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: invalid parameters (etcp=%p, links=%p)", etcp, etcp ? etcp->links : NULL); @@ -37,8 +35,6 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) { uint64_t min_load_tb = UINT64_MAX; uint64_t now_tb=get_current_time_units(); - DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: current tb=%llu, scanning links", (unsigned long long)now_tb); - struct ETCP_LINK* link = etcp->links; int link_index = 0; while (link) { diff --git a/src/utun_instance.c b/src/utun_instance.c index 2cd88fe2..e03ec1a6 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -36,7 +36,6 @@ void utun_instance_set_tun_init_enabled(int enabled) { // Create and initialize root instance struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config_file) { - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "blablabla 11"); struct UTUN_INSTANCE *instance = calloc(1, sizeof(struct UTUN_INSTANCE)); if (!instance) return NULL; @@ -47,7 +46,6 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config // Note: Global debug system is initialized from command line arguments if provided // If not initialized via command line, instance-specific logging can be set up here // The first initialization wins - either global (from main) or instance-specific - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "blablabla 11"); // Ensure keys and node_id exist in config if (config_ensure_keys_and_node_id(config_file) < 0) { @@ -56,7 +54,6 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config return NULL; } - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "blablabla 12"); // Load configuration instance->config = parse_config(config_file); @@ -78,9 +75,6 @@ struct UTUN_INSTANCE* utun_instance_create(struct UASYNC* ua, const char *config DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to initialize local keys"); } - DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "blablabla 2"); - - instance->ack_pool=memory_pool_init(sizeof(struct ACK_PACKET)); instance->data_pool=memory_pool_init(PACKET_DATA_SIZE); instance->pkt_pool=memory_pool_init(sizeof(struct ETCP_DGRAM) + PACKET_DATA_SIZE); diff --git a/tests/Makefile.am b/tests/Makefile.am index fd7d47d5..35ba4508 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -41,7 +41,7 @@ test_crypto_SOURCES = test_crypto.c test_crypto_CFLAGS = -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source -I$(top_srcdir)/lib test_crypto_LDADD = $(top_builddir)/tinycrypt/lib/source/utun-aes_encrypt.o $(top_builddir)/tinycrypt/lib/source/utun-aes_decrypt.o $(top_builddir)/tinycrypt/lib/source/utun-ccm_mode.o $(top_builddir)/tinycrypt/lib/source/utun-cmac_mode.o $(top_builddir)/tinycrypt/lib/source/utun-ctr_mode.o $(top_builddir)/tinycrypt/lib/source/utun-sha256.o $(top_builddir)/tinycrypt/lib/source/utun-utils.o $(top_builddir)/lib/libuasync.a -lpthread -lcrypto -# New comprehensive ll_queue test with current API +# Working ll_queue test for xxx=0 architecture test_ll_queue_SOURCES = test_ll_queue.c test_ll_queue_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib test_ll_queue_LDADD = $(top_builddir)/lib/libuasync.a -lpthread -lcrypto diff --git a/tests/test_ll_queue.c b/tests/test_ll_queue.c index a432adc2..4a36da1e 100644 --- a/tests/test_ll_queue.c +++ b/tests/test_ll_queue.c @@ -1,6 +1,5 @@ /** - * Comprehensive unit tests for ll_queue module with current API - * Tests: basic operations, callback system, waiters, hash table, memory pool, edge cases, stress tests + * Компактные, логичные unit-тесты для ll_queue (embedded ll_entry + xxx=0) */ #include @@ -16,1111 +15,284 @@ #include "../lib/debug_config.h" #include "../lib/memory_pool.h" -/* Enable debug output for ll_queue */ #define DEBUG_CATEGORY_LL_QUEUE 1 -/* Test statistics */ static struct { - int tests_run; - int tests_passed; - int tests_failed; - - /* Callback counters */ - int queue_callback_count; - int waiter_callback_count; - - /* Error tracking */ - int memory_errors; - int hash_table_errors; - int ref_count_errors; - - /* Performance metrics */ - long total_operations; - double total_time_ms; -} test_stats = {0}; + int run, passed, failed; + int cb_queue, cb_waiter; + int mem_err, hash_err; + long ops; + double time_ms; +} stats = {0}; -/* Test result tracking */ -#define TEST_START(name) do { \ - printf("TEST: %s... ", name); \ - test_stats.tests_run++; \ - fflush(stdout); \ +#define TEST(name) do { \ + printf("TEST: %-35s ", name); fflush(stdout); \ + stats.run++; \ } while(0) -#define TEST_PASS() do { \ - printf("PASS\n"); \ - test_stats.tests_passed++; \ -} while(0) - -#define TEST_FAIL(msg) do { \ - printf("FAIL: %s\n", msg); \ - test_stats.tests_failed++; \ -} while(0) +#define PASS() do { puts("PASS"); stats.passed++; } while(0) +#define FAIL(msg) do { printf("FAIL: %s\n", msg); stats.failed++; } while(0) -#define ASSERT_TRUE(cond, msg) do { \ - if (!(cond)) { TEST_FAIL(msg); return; } \ -} while(0) - -#define ASSERT_FALSE(cond, msg) ASSERT_TRUE(!(cond), msg) -#define ASSERT_NULL(ptr, msg) ASSERT_TRUE((ptr) == NULL, msg) -#define ASSERT_NOT_NULL(ptr, msg) ASSERT_TRUE((ptr) != NULL, msg) -#define ASSERT_EQ(a, b, msg) ASSERT_TRUE((a) == (b), msg) -#define ASSERT_NEQ(a, b, msg) ASSERT_TRUE((a) != (b), msg) -#define ASSERT_STR_EQ(a, b, msg) ASSERT_TRUE(strcmp((a), (b)) == 0, msg) -#define ASSERT_GE(a, b, msg) ASSERT_TRUE((a) >= (b), msg) -#define ASSERT_LE(a, b, msg) ASSERT_TRUE((a) <= (b), msg) -#define ASSERT_GT(a, b, msg) ASSERT_TRUE((a) > (b), msg) -#define ASSERT_LT(a, b, msg) ASSERT_TRUE((a) < (b), msg) +#define ASSERT(c, m) do { if (!(c)) { FAIL(m); return; } } while(0) +#define ASSERT_EQ(a,b,m) ASSERT((a)==(b),m) -/* Utility functions */ -static double get_time_ms() { - struct timeval tv; - gettimeofday(&tv, NULL); +/* ------------------------------------------------------------------ */ +static double now_ms(void) { + struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; } -/* Test data structure */ typedef struct { + struct ll_entry ll; // ← embedded, first field int id; char name[32]; int value; uint64_t checksum; } test_data_t; -static uint64_t compute_checksum(test_data_t* data) { - return (uint64_t)data->id * 31 + (uint64_t)data->value * 17 + (uint64_t)strlen(data->name); +static uint64_t checksum(const test_data_t *d) { + return (uint64_t)d->id * 31 + (uint64_t)d->value * 17 + strlen(d->name); } -/* Queue callback for testing */ -static void test_queue_callback(struct ll_queue* q, void* data, void* arg) { - int* callback_count = (int*)arg; - (*callback_count)++; - test_stats.queue_callback_count++; - - /* Process the data (simulate work) */ - test_data_t* test_data = (test_data_t*)data; - ASSERT_TRUE(compute_checksum(test_data) == test_data->checksum, "Data corruption in callback"); - - /* Don't call resume_callback if this is the last test to avoid timer issues */ - if (*callback_count < 10) { /* Limit callbacks to prevent infinite loop */ - /* Simulate async processing by calling resume after a delay */ - queue_resume_callback(q); - } -} +/* Callback consumer: забирает и освобождает элемент */ +static void queue_cb(struct ll_queue *q, void *data, void *arg) { + int *cnt = arg; + (*cnt)++; stats.cb_queue++; + + test_data_t *td = data; + ASSERT(checksum(td) == td->checksum, "corruption in cb"); + + void *taken = queue_data_get(q); + ASSERT(taken == data, "get != passed data"); + queue_data_free(taken); -/* Waiter callback for testing */ -static void test_waiter_callback(struct ll_queue* q, void* arg) { - int* callback_count = (int*)arg; - (*callback_count)++; - test_stats.waiter_callback_count++; + queue_resume_callback(q); // next item when ready } - /* Test 1: Basic queue creation and destruction */ -static void test_basic_creation() { - TEST_START("Basic queue creation and destruction"); - - struct UASYNC* ua = uasync_create(); - ASSERT_NOT_NULL(ua, "Failed to create uasync instance"); - - /* Test with no hash table */ - struct ll_queue* q = queue_new(ua, 0); - ASSERT_NOT_NULL(q, "Failed to create queue"); - ASSERT_EQ(queue_entry_count(q), 0, "New queue should be empty"); - ASSERT_EQ(queue_total_bytes(q), 0, "New queue should have 0 bytes"); - - /* Test with hash table */ - struct ll_queue* q_hash = queue_new(ua, 16); - ASSERT_NOT_NULL(q_hash, "Failed to create queue with hash table"); - ASSERT_EQ(queue_entry_count(q_hash), 0, "New hash queue should be empty"); - - queue_free(q); - queue_free(q_hash); - uasync_destroy(ua, 0); - - TEST_PASS(); +static void waiter_cb(struct ll_queue *q, void *arg) { + (*(int*)arg)++; stats.cb_waiter++; } -/* Test 2: Data creation and basic operations */ -static void test_data_operations() { - TEST_START("Data creation and basic operations"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - /* Create test data */ - test_data_t* data1 = (test_data_t*)queue_data_new(sizeof(test_data_t)); - ASSERT_NOT_NULL(data1, "Failed to create data"); - data1->id = 1; - strcpy(data1->name, "test1"); - data1->value = 100; - data1->checksum = compute_checksum(data1); - - /* Put data into queue */ - ASSERT_EQ(queue_data_put(q, data1, data1->id), 0, "Failed to put data"); - ASSERT_EQ(queue_entry_count(q), 1, "Queue should have 1 element"); - ASSERT_GE(queue_total_bytes(q), sizeof(test_data_t), "Queue should have at least data size"); - - /* Get data from queue */ - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Failed to get data"); - ASSERT_EQ(retrieved->id, 1, "Wrong data retrieved"); - ASSERT_STR_EQ(retrieved->name, "test1", "Wrong name retrieved"); - ASSERT_EQ(retrieved->value, 100, "Wrong value retrieved"); - ASSERT_EQ(retrieved->checksum, compute_checksum(retrieved), "Checksum mismatch"); - - /* Queue should be empty now */ - ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty after get"); - - /* Free the data */ - queue_data_free(retrieved); - - queue_free(q); +/* ------------------------------------------------------------------ */ +static void test_basic(void) { + TEST("basic creation / free"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q1 = queue_new(ua, 0); + struct ll_queue *q2 = queue_new(ua, 16); + ASSERT(q1 && q2, "queue_new failed"); + ASSERT_EQ(queue_entry_count(q1), 0, ""); + queue_free(q1); queue_free(q2); uasync_destroy(ua, 0); - - TEST_PASS(); + PASS(); } -/* Test 3: FIFO ordering */ -static void test_fifo_ordering() { - TEST_START("FIFO ordering"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - const int num_items = 10; - test_data_t* items[num_items]; - - /* Create and put items in order */ - for (int i = 0; i < num_items; i++) { - items[i] = (test_data_t*)queue_data_new(sizeof(test_data_t)); - items[i]->id = i; - snprintf(items[i]->name, sizeof(items[i]->name), "item_%d", i); - items[i]->value = i * 10; - items[i]->checksum = compute_checksum(items[i]); - - ASSERT_EQ(queue_data_put(q, items[i], items[i]->id), 0, "Failed to put data"); +static void test_fifo(void) { + TEST("FIFO ordering"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 0); + + for (int i = 0; i < 10; i++) { + test_data_t *d = queue_data_new(sizeof(test_data_t)); + d->id = i; snprintf(d->name, sizeof(d->name), "item%d", i); + d->value = i*10; d->checksum = checksum(d); + queue_data_put(q, d, d->id); } - - ASSERT_EQ(queue_entry_count(q), num_items, "Wrong item count"); - - /* Retrieve and verify FIFO order */ - for (int i = 0; i < num_items; i++) { - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Failed to get data"); - ASSERT_EQ(retrieved->id, i, "FIFO order violated"); - ASSERT_EQ(retrieved->value, i * 10, "Wrong value in FIFO"); - - queue_data_free(retrieved); + ASSERT_EQ(queue_entry_count(q), 10, ""); + + for (int i = 0; i < 10; i++) { + test_data_t *d = queue_data_get(q); + ASSERT(d && d->id == i, "FIFO violation"); + queue_data_free(d); } - - ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); + ASSERT_EQ(queue_entry_count(q), 0, ""); + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Test 4: LIFO operations with put_first */ -static void test_lifo_operations() { - TEST_START("LIFO operations with put_first"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - /* Add some items normally (FIFO) */ +static void test_lifo_priority(void) { + TEST("put_first (LIFO priority)"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 0); + for (int i = 0; i < 3; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = i; - snprintf(data->name, sizeof(data->name), "normal_%d", i); - data->value = i; - data->checksum = compute_checksum(data); - - ASSERT_EQ(queue_data_put(q, data, data->id), 0, "Failed to put data"); + test_data_t *d = queue_data_new(sizeof(*d)); + d->id = i; + d->value = i; + d->checksum = checksum(d); + queue_data_put(q, d, i); } - - /* Add high priority item at the beginning */ - test_data_t* priority = (test_data_t*)queue_data_new(sizeof(test_data_t)); - priority->id = 999; - strcpy(priority->name, "priority"); - priority->value = 999; - priority->checksum = compute_checksum(priority); - - ASSERT_EQ(queue_data_put_first(q, priority, priority->id), 0, "Failed to put_first data"); - - /* First item out should be the priority one */ - test_data_t* first = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(first, "Failed to get data"); - ASSERT_EQ(first->id, 999, "Priority item should come first"); - ASSERT_STR_EQ(first->name, "priority", "Wrong priority item"); + + test_data_t *pri = queue_data_new(sizeof(*pri)); + pri->id = 999; pri->value = 999; pri->checksum = checksum(pri); + queue_data_put_first(q, pri, 999); + + test_data_t *first = queue_data_get(q); + ASSERT(first && first->id == 999, "priority first"); queue_data_free(first); - - /* Remaining items should be in original FIFO order */ + for (int i = 0; i < 3; i++) { - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Failed to get data"); - ASSERT_EQ(retrieved->id, i, "FIFO order violated after priority"); - queue_data_free(retrieved); + test_data_t *d = queue_data_get(q); + ASSERT(d && d->id == i, "remaining FIFO"); + queue_data_free(d); } - - ASSERT_EQ(queue_entry_count(q), 0, "Queue should be empty"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Test 5: Callback system - basic */ -static void test_callback_basic() { - TEST_START("Callback system - basic"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - int callback_count = 0; - queue_set_callback(q, test_queue_callback, &callback_count); - - /* Add first item - callback should be called */ - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = 1; - strcpy(data->name, "callback_test"); - data->value = 42; - data->checksum = compute_checksum(data); - - printf("\n Before put: queue_count=%d", queue_entry_count(q)); - ASSERT_EQ(queue_data_put(q, data, data->id), 0, "Failed to put data"); - printf(" After put: queue_count=%d", queue_entry_count(q)); - - /* Process events to trigger callback */ - for (int i = 0; i < 10; i++) { - uasync_poll(ua, 1); /* 0.1ms poll */ - printf(" After poll %d: queue_count=%d, callback_count=%d", i, queue_entry_count(q), callback_count); - } - - ASSERT_GE(callback_count, 1, "Callback should be called at least once"); - printf(" Final queue_count=%d", queue_entry_count(q)); - - /* IMPORTANT: элемент остается в очереди после callback, не освобождать вручную! */ - /* queue_free должна освободить оставшиеся элементы */ - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} +static void test_callback(void) { + TEST("callback serial processing"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 0); + + int cnt = 0; + queue_set_callback(q, queue_cb, &cnt); -/* Test 6: Callback suspension and resumption */ -static void test_callback_suspension() { - TEST_START("Callback suspension and resumption"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - int callback_count = 0; - queue_set_callback(q, test_queue_callback, &callback_count); - - /* Add multiple items quickly */ for (int i = 0; i < 5; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = i; - snprintf(data->name, sizeof(data->name), "item_%d", i); - data->value = i * 10; - data->checksum = compute_checksum(data); - - ASSERT_EQ(queue_data_put(q, data, data->id), 0, "Failed to put data"); - } - - /* Process events - callback should process items one by one */ - for (int i = 0; i < 50; i++) { - uasync_poll(ua, 1); + test_data_t *d = queue_data_new(sizeof(*d)); + d->id = i; d->value = i*10; d->checksum = checksum(d); + queue_data_put(q, d, d->id); } - - /* Callback should have been called for each item */ - ASSERT_GE(callback_count, 1, "Callback should process items"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} -/* Test 7: Waiter mechanism - basic */ -static void test_waiter_basic() { - TEST_START("Waiter mechanism - basic"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - int waiter_called = 0; - - /* Register waiter for empty queue - should trigger immediately */ - struct queue_waiter* waiter = queue_wait_threshold(q, 0, 0, test_waiter_callback, &waiter_called); - if (waiter_called == 1) { - /* Callback already called, waiter may be NULL or not */ - /* If waiter is not NULL, it might have been auto-freed, but we ignore */ - } else { - /* Callback not called yet */ - ASSERT_EQ(waiter_called, 0, "Callback should not be called yet"); - if (waiter != NULL) { - /* Waiter was created, callback should be called asynchronously */ - /* Process events to trigger callback */ - for (int i = 0; i < 10; i++) { - uasync_poll(ua, 1); - } - ASSERT_EQ(waiter_called, 1, "Waiter callback should be triggered asynchronously"); - } else { - /* No waiter created, but callback should have been called */ - TEST_FAIL("Expected waiter_called == 1 or waiter != NULL"); - } - } - - /* Reset and test with non-empty queue */ - waiter_called = 0; - - /* Add some items */ - for (int i = 0; i < 3; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = i; - queue_data_put(q, data, data->id); - } - - /* Register waiter for threshold */ - waiter = queue_wait_threshold(q, 2, 0, test_waiter_callback, &waiter_called); - ASSERT_NOT_NULL(waiter, "Waiter should be registered when condition not met"); - ASSERT_EQ(waiter_called, 0, "Callback should not be called yet"); - - /* Remove items to reach threshold */ - for (int i = 0; i < 2; i++) { - test_data_t* data = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(data, "Failed to get data"); - queue_data_free(data); - } - - /* Process events to trigger waiter */ - for (int i = 0; i < 10; i++) { - uasync_poll(ua, 1); - } - - ASSERT_EQ(waiter_called, 1, "Waiter callback should be triggered"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); + for (int i = 0; i < 30 && cnt < 5; i++) uasync_poll(ua, 5); + ASSERT_EQ(cnt, 5, "all items processed via callback"); + ASSERT_EQ(queue_entry_count(q), 0, ""); + + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Test 8: Waiter cancellation */ -static void test_waiter_cancellation() { - TEST_START("Waiter cancellation"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - int waiter_called = 0; - - /* Add items */ +static void test_waiter(void) { + TEST("wait_threshold + cancel"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 0); + + int called = 0; + struct queue_waiter *w = queue_wait_threshold(q, 2, 0, waiter_cb, &called); + ASSERT(w == NULL && called == 1, "immediate when condition met"); // empty queue + for (int i = 0; i < 5; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = i; - queue_data_put(q, data, data->id); - } - - /* Register waiter */ - struct queue_waiter* waiter = queue_wait_threshold(q, 2, 0, test_waiter_callback, &waiter_called); - ASSERT_NOT_NULL(waiter, "Waiter should be registered"); - - /* Cancel the waiter */ - queue_cancel_wait(q, waiter); - - /* Reach the threshold - waiter should not be called */ - for (int i = 0; i < 3; i++) { - test_data_t* data = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(data, "Failed to get data"); - queue_data_free(data); + test_data_t *d = queue_data_new(sizeof(*d)); d->id = i; + queue_data_put(q, d, i); } - - /* Process events */ - for (int i = 0; i < 10; i++) { - uasync_poll(ua, 1); - } - - ASSERT_EQ(waiter_called, 0, "Cancelled waiter should not be called"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); + + called = 0; + w = queue_wait_threshold(q, 2, 0, waiter_cb, &called); + ASSERT(w != NULL && called == 0, ""); + + for (int i = 0; i < 3; i++) queue_data_free(queue_data_get(q)); + + for (int i = 0; i < 15; i++) uasync_poll(ua, 1); + ASSERT_EQ(called, 1, ""); + + queue_cancel_wait(q, w); + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Test 9: Size limits */ -static void test_size_limits() { - TEST_START("Size limits"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - /* Set size limit to 3 */ +static void test_limits_hash(void) { + TEST("size limit + hash find/remove"); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 16); queue_set_size_limit(q, 3); - - /* Add up to limit */ + for (int i = 0; i < 3; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = i; - ASSERT_EQ(queue_data_put(q, data, data->id), 0, "Should accept items up to limit"); + test_data_t *d = queue_data_new(sizeof(*d)); d->id = i*10+1; + queue_data_put(q, d, d->id); } - - ASSERT_EQ(queue_entry_count(q), 3, "Queue should be at limit"); - - /* Try to add one more - should be rejected */ - test_data_t* excess = (test_data_t*)queue_data_new(sizeof(test_data_t)); - excess->id = 999; - ASSERT_EQ(queue_data_put(q, excess, excess->id), -1, "Should reject items beyond limit"); - - /* Excess data should be automatically freed by queue */ - /* We'll verify by checking memory consistency later */ - - /* Remove one item, should be able to add again */ - test_data_t* removed = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(removed, "Should get item"); - queue_data_free(removed); - - ASSERT_EQ(queue_entry_count(q), 2, "Queue should have 2 items"); - - test_data_t* new_data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - new_data->id = 100; - ASSERT_EQ(queue_data_put(q, new_data, new_data->id), 0, "Should accept new item after removal"); - - ASSERT_EQ(queue_entry_count(q), 3, "Queue should be at limit again"); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} + test_data_t *ex = queue_data_new(sizeof(*ex)); + ASSERT_EQ(queue_data_put(q, ex, 999), -1, "limit reject"); -/* Test 10: Hash table operations (ID-based search) */ -static void test_hash_table_operations() { - TEST_START("Hash table operations (ID-based search)"); - - struct UASYNC* ua = uasync_create(); - /* Create queue with hash table size 16 */ - struct ll_queue* q = queue_new(ua, 16); - ASSERT_NOT_NULL(q, "Failed to create queue with hash table"); - - const int num_items = 10; - test_data_t* items[num_items]; - - /* Create items with known IDs */ - for (int i = 0; i < num_items; i++) { - items[i] = (test_data_t*)queue_data_new(sizeof(test_data_t)); - items[i]->id = i * 10 + 1; /* IDs: 1, 11, 21, ... */ - snprintf(items[i]->name, sizeof(items[i]->name), "item_%d", items[i]->id); - items[i]->value = i * 100; - items[i]->checksum = compute_checksum(items[i]); - - ASSERT_EQ(queue_data_put(q, items[i], items[i]->id), 0, "Failed to put data"); - } - - /* Find items by ID */ - for (int i = 0; i < num_items; i++) { - uint32_t target_id = i * 10 + 1; - test_data_t* found = (test_data_t*)queue_find_data_by_id(q, target_id); - ASSERT_NOT_NULL(found, "Failed to find item by ID"); - ASSERT_EQ(found->id, target_id, "Wrong item found"); - ASSERT_EQ(found->value, i * 100, "Wrong value in found item"); - } - - /* Test non-existent ID */ - test_data_t* not_found = (test_data_t*)queue_find_data_by_id(q, 99999); - ASSERT_NULL(not_found, "Should return NULL for non-existent ID"); - - /* Remove item by pointer */ - test_data_t* to_remove = (test_data_t*)queue_find_data_by_id(q, 21); /* Third item */ - ASSERT_NOT_NULL(to_remove, "Item to remove should exist"); - ASSERT_EQ(queue_remove_data(q, to_remove), 0, "Failed to remove data by pointer"); - - /* Verify removal */ - test_data_t* should_be_null = (test_data_t*)queue_find_data_by_id(q, 21); - ASSERT_NULL(should_be_null, "Removed item should not be found"); - ASSERT_EQ(queue_entry_count(q), num_items - 1, "Queue count should decrease"); - - /* Clean up remaining items */ - for (int i = 0; i < num_items; i++) { - if (i == 2) continue; /* Already removed */ - test_data_t* data = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(data, "Failed to get data"); - queue_data_free(data); - } - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} + test_data_t *found = queue_find_data_by_id(q, 21); + ASSERT(found && found->id == 21, "hash find"); + queue_remove_data(q, found); + ASSERT(queue_find_data_by_id(q, 21) == NULL, "removed"); -/* Test 11: Memory pool integration */ -static void test_memory_pool_integration() { - TEST_START("Memory pool integration"); - - struct UASYNC* ua = uasync_create(); - - /* Create memory pool for test data (должен вмещать struct ll_entry + test_data_t) */ - struct memory_pool* pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(test_data_t)); - ASSERT_NOT_NULL(pool, "Failed to create memory pool"); - - /* Create queue */ - struct ll_queue* q = queue_new(ua, 0); - ASSERT_NOT_NULL(q, "Failed to create queue"); - - /* Test basic pool operations */ - size_t initial_allocations = 0, initial_reuse = 0; - memory_pool_get_stats(pool, &initial_allocations, &initial_reuse); - - /* Create data from pool */ - test_data_t* data1 = (test_data_t*)queue_data_new_from_pool(pool); - ASSERT_NOT_NULL(data1, "Failed to create data from pool"); - - /* Check pool stats after allocation */ - size_t after_allocations = 0, after_reuse = 0; - memory_pool_get_stats(pool, &after_allocations, &after_reuse); - ASSERT_GT(after_allocations, initial_allocations, "Pool allocations should increase"); - - /* Initialize data */ - data1->id = 1; - strcpy(data1->name, "pool_test_1"); - data1->value = 100; - data1->checksum = compute_checksum(data1); - - /* Put data in queue */ - ASSERT_EQ(queue_data_put(q, data1, data1->id), 0, "Failed to put pooled data"); - ASSERT_EQ(queue_entry_count(q), 1, "Queue should have 1 element"); - - /* Create more data from pool */ - test_data_t* data2 = (test_data_t*)queue_data_new_from_pool(pool); - ASSERT_NOT_NULL(data2, "Failed to create second data from pool"); - data2->id = 2; - strcpy(data2->name, "pool_test_2"); - data2->value = 200; - data2->checksum = compute_checksum(data2); - - ASSERT_EQ(queue_data_put(q, data2, data2->id), 0, "Failed to put second pooled data"); - ASSERT_EQ(queue_entry_count(q), 2, "Queue should have 2 elements"); - - /* Retrieve and free data (should go back to pool) */ - test_data_t* retrieved1 = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved1, "Failed to get first data"); - ASSERT_EQ(retrieved1->id, 1, "Wrong data retrieved"); - - /* Check pool stats before free */ - size_t before_free_allocations = 0, before_free_reuse = 0; - memory_pool_get_stats(pool, &before_free_allocations, &before_free_reuse); - - /* Free data (should return to pool) */ - queue_data_free(retrieved1); - - /* Check pool stats after free */ - size_t after_free_allocations = 0, after_free_reuse = 0; - memory_pool_get_stats(pool, &after_free_allocations, &after_free_reuse); - printf("\n Pool stats: before_free=(alloc=%zu, reuse=%zu), after_free=(alloc=%zu, reuse=%zu)", - before_free_allocations, before_free_reuse, after_free_allocations, after_free_reuse); - ASSERT_EQ(after_free_allocations, before_free_allocations, "Allocations should not change after free"); - /* Note: reuse count may not increase if pool implementation is different */ - if (after_free_reuse > before_free_reuse) { - printf(" ✓ Reuse increased as expected"); - } else { - printf(" ⚠ Reuse did not increase (may be expected behavior)"); - } - - /* Create another data (should reuse from pool) */ - test_data_t* data3 = (test_data_t*)queue_data_new_from_pool(pool); - ASSERT_NOT_NULL(data3, "Failed to create third data from pool"); - - /* Check reuse stats */ - size_t final_allocations = 0, final_reuse = 0; - memory_pool_get_stats(pool, &final_allocations, &final_reuse); - printf("\n Pool stats: final=(alloc=%zu, reuse=%zu)", final_allocations, final_reuse); - ASSERT_EQ(final_allocations, after_free_allocations, "No new allocations for reused data"); - /* Note: reuse behavior depends on pool implementation */ - if (final_reuse > after_free_reuse) { - printf(" ✓ Final reuse increased"); - } else { - printf(" ⚠ Final reuse did not increase (implementation dependent)"); - } - - /* Clean up data3 */ - queue_data_free(data3); - - /* Clean up remaining data */ - test_data_t* retrieved2 = (test_data_t*)queue_data_get(q); - if (retrieved2) { - queue_data_free(retrieved2); - } - - queue_free(q); - memory_pool_destroy(pool); - uasync_destroy(ua, 0); - - TEST_PASS(); + while (queue_entry_count(q)) queue_data_free(queue_data_get(q)); + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Test 12: Edge cases and error handling */ -static void test_edge_cases() { - TEST_START("Edge cases and error handling"); - - /* Test NULL parameters */ - ASSERT_NULL(queue_new(NULL, 0), "Should return NULL for NULL uasync"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 0); - - /* Test NULL queue operations */ - queue_set_callback(NULL, NULL, NULL); /* Should not crash */ - queue_resume_callback(NULL); - queue_set_size_limit(NULL, 10); - - ASSERT_NULL(queue_data_get(NULL), "Should return NULL for NULL queue"); - ASSERT_EQ(queue_data_put(NULL, NULL, 0), -1, "Should fail for NULL queue"); - ASSERT_EQ(queue_data_put_first(NULL, NULL, 0), -1, "Should fail for NULL queue"); - - ASSERT_EQ(queue_entry_count(NULL), 0, "Should return 0 for NULL queue"); - ASSERT_EQ(queue_total_bytes(NULL), 0, "Should return 0 for NULL queue"); - - ASSERT_NULL(queue_find_data_by_id(NULL, 0), "Should return NULL for NULL queue"); - ASSERT_EQ(queue_remove_data(NULL, NULL), -1, "Should fail for NULL queue"); - - /* Test with zero-sized data */ - void* zero_data = queue_data_new(0); - ASSERT_NOT_NULL(zero_data, "Should handle zero-sized data"); - ASSERT_EQ(queue_data_put(q, zero_data, 1), 0, "Should accept zero-sized data"); - ASSERT_EQ(queue_entry_count(q), 1, "Should count zero-sized data"); - - void* retrieved = queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Should retrieve zero-sized data"); - queue_data_free(retrieved); - - /* Test adding multiple items with different IDs */ - test_data_t* data1 = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data1->id = 100; - ASSERT_EQ(queue_data_put(q, data1, data1->id), 0, "Should accept first item with ID"); - - test_data_t* data2 = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data2->id = 101; /* Different ID */ - ASSERT_EQ(queue_data_put(q, data2, data2->id), 0, "Should accept second item with different ID"); - - /* Both items should be findable */ - test_data_t* found1 = (test_data_t*)queue_find_data_by_id(q, 100); - test_data_t* found2 = (test_data_t*)queue_find_data_by_id(q, 101); - /* Note: find only works if queue was created with hash_size > 0 */ - /* If hash_size == 0, find will return NULL, which is acceptable */ - /* We'll just clean up regardless */ - - /* Clean up */ - test_data_t* d1 = (test_data_t*)queue_data_get(q); - test_data_t* d2 = (test_data_t*)queue_data_get(q); - if (d1) queue_data_free(d1); - if (d2) queue_data_free(d2); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} +static void test_pool(void) { + TEST("memory_pool integration + reuse"); + struct UASYNC *ua = uasync_create(); + struct memory_pool *pool = memory_pool_init(sizeof(test_data_t)); + struct ll_queue *q = queue_new(ua, 0); -/* Test 13: Stress test - high volume operations */ -static void test_stress_high_volume() { - TEST_START("Stress test - high volume operations"); - - double start_time = get_time_ms(); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 64); /* With hash table */ - - const int num_operations = 10000; - const int max_queue_size = 100; - - srand(time(NULL)); - - for (int i = 0; i < num_operations; i++) { - int op = rand() % 100; - - if (op < 40) { /* 40%: Add to tail */ - if (queue_entry_count(q) < max_queue_size) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - if (data) { - data->id = rand() % 1000; - data->value = rand(); - data->checksum = compute_checksum(data); - queue_data_put(q, data, data->id); - test_stats.total_operations++; - } - } - } else if (op < 60) { /* 20%: Add to head (priority) */ - if (queue_entry_count(q) < max_queue_size) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - if (data) { - data->id = rand() % 1000; - data->value = rand(); - data->checksum = compute_checksum(data); - queue_data_put_first(q, data, data->id); - test_stats.total_operations++; - } - } - } else if (op < 80) { /* 20%: Remove from head */ - test_data_t* data = (test_data_t*)queue_data_get(q); - if (data) { - if (data->checksum != compute_checksum(data)) { - test_stats.memory_errors++; - } - queue_data_free(data); - test_stats.total_operations++; - } - } else if (op < 90) { /* 10%: Find by ID */ - uint32_t search_id = rand() % 1000; - test_data_t* found = (test_data_t*)queue_find_data_by_id(q, search_id); - if (found) { - if (found->checksum != compute_checksum(found)) { - test_stats.hash_table_errors++; - } - test_stats.total_operations++; - } - } else { /* 10%: Remove by pointer */ - if (queue_entry_count(q) > 0) { - /* Find random item to remove */ - int target_idx = rand() % queue_entry_count(q); - struct ll_entry* curr = q->head; - for (int j = 0; j < target_idx && curr; j++) { - curr = curr->next; - } - if (curr) { - test_data_t* data = (test_data_t*)(curr->payload); - if (queue_remove_data(q, data) == 0) { - queue_data_free(data); - test_stats.total_operations++; - } - } - } - } - - /* Periodic integrity check */ - if (i % 1000 == 0) { - /* Verify doubly-linked list integrity */ - int count = 0; - struct ll_entry* curr = q->head; - struct ll_entry* prev = NULL; - while (curr) { - count++; - if (curr->prev != prev) { - test_stats.memory_errors++; - } - if (prev && prev->next != curr) { - test_stats.memory_errors++; - } - prev = curr; - curr = curr->next; - } - if (count != queue_entry_count(q)) { - test_stats.memory_errors++; - } - - /* Verify hash table integrity */ - for (uint32_t id = 0; id < 1000; id += 10) { - test_data_t* found = (test_data_t*)queue_find_data_by_id(q, id); - if (found) { - /* Verify the found item is actually in the queue */ - int found_in_list = 0; - curr = q->head; - while (curr) { - if ((test_data_t*)(curr->payload) == found) { - found_in_list = 1; - break; - } - curr = curr->next; - } - if (!found_in_list) { - test_stats.hash_table_errors++; - } - } - } - } - } - - double end_time = get_time_ms(); - test_stats.total_time_ms += (end_time - start_time); - - /* Clean up remaining items */ - while (queue_entry_count(q) > 0) { - test_data_t* data = (test_data_t*)queue_data_get(q); - if (data) queue_data_free(data); - } - - ASSERT_EQ(test_stats.memory_errors, 0, "Memory corruption detected during stress test"); - ASSERT_EQ(test_stats.hash_table_errors, 0, "Hash table corruption detected"); - ASSERT_TRUE(test_stats.total_operations > num_operations / 2, "Too few operations performed"); - - printf("\n Operations: %ld, Time: %.2f ms, Ops/sec: %.2f", - test_stats.total_operations, end_time - start_time, - test_stats.total_operations * 1000.0 / (end_time - start_time)); - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); -} + size_t alloc1 = 0, reuse1 = 0; + memory_pool_get_stats(pool, &alloc1, &reuse1); -/* Test 15: Memory pool stress test */ -static void test_memory_pool_stress() { - TEST_START("Memory pool stress test"); - - struct UASYNC* ua = uasync_create(); - - /* Create memory pool (должен вмещать struct ll_entry + test_data_t) */ - struct memory_pool* pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(test_data_t)); - ASSERT_NOT_NULL(pool, "Failed to create memory pool"); - - /* Create queue */ - struct ll_queue* q = queue_new(ua, 0); - ASSERT_NOT_NULL(q, "Failed to create queue"); - - const int iterations = 1000; - test_data_t* items[iterations]; - - /* Stress test: allocate and free many items */ - size_t initial_allocations = 0, initial_reuse = 0; - memory_pool_get_stats(pool, &initial_allocations, &initial_reuse); - - /* Allocate many items from pool */ - for (int i = 0; i < iterations; i++) { - items[i] = (test_data_t*)queue_data_new_from_pool(pool); - ASSERT_NOT_NULL(items[i], "Failed to allocate from pool"); - - items[i]->id = i; - snprintf(items[i]->name, sizeof(items[i]->name), "stress_%d", i); - items[i]->value = i * 10; - items[i]->checksum = compute_checksum(items[i]); - } - - /* Check allocations increased */ - size_t after_allocations = 0, after_reuse = 0; - memory_pool_get_stats(pool, &after_allocations, &after_reuse); - ASSERT_GT(after_allocations, initial_allocations, "Pool allocations should increase"); - - /* Put all items in queue */ - for (int i = 0; i < iterations; i++) { - ASSERT_EQ(queue_data_put(q, items[i], items[i]->id), 0, "Failed to put stress data"); - } - ASSERT_EQ(queue_entry_count(q), iterations, "Queue should have all stress items"); - - /* Remove half of items */ - for (int i = 0; i < iterations / 2; i++) { - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Failed to get stress data"); - queue_data_free(retrieved); /* Should return to pool */ - } - - /* Check reuse count increased */ - size_t after_free_allocations = 0, after_free_reuse = 0; - memory_pool_get_stats(pool, &after_free_allocations, &after_free_reuse); - ASSERT_EQ(after_free_allocations, after_allocations, "Pool allocations should not change"); - /* Note: reuse count may not increase if pool implementation is different - skip check in stress test */ - - /* Final stats check */ - size_t final_allocations = 0, final_reuse = 0; - memory_pool_get_stats(pool, &final_allocations, &final_reuse); - printf("\n Final pool stats: allocations=%zu, reuse=%zu", final_allocations, final_reuse); - - /* Should not allocate more than initial pool size */ - ASSERT_LE(final_allocations, after_allocations + 50, "Should not allocate much more than initial"); - /* Note: reuse behavior depends on pool implementation and usage patterns - skip strict check for stress test */ - - /* Clean up remaining items */ - while (queue_entry_count(q) > 0) { - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - if (retrieved) { - queue_data_free(retrieved); - } - } - - queue_free(q); - memory_pool_destroy(pool); - uasync_destroy(ua, 0); - - TEST_PASS(); + test_data_t *d1 = queue_data_new_from_pool(pool); + d1->id = 1; d1->checksum = checksum(d1); + queue_data_put(q, d1, 1); + + test_data_t *d2 = queue_data_new_from_pool(pool); + d2->id = 2; d2->checksum = checksum(d2); + queue_data_put(q, d2, 2); + + queue_data_free(queue_data_get(q)); // free d1 back to pool + queue_data_free(queue_data_get(q)); // free d2 back to pool + + // Now allocate again to trigger reuse + test_data_t *d3 = queue_data_new_from_pool(pool); + ASSERT(d3 != NULL, "alloc after free failed"); + d3->id = 3; d3->checksum = checksum(d3); + queue_data_put(q, d3, 3); + queue_data_free(queue_data_get(q)); // free d3 back + + size_t alloc2 = 0, reuse2 = 0; + memory_pool_get_stats(pool, &alloc2, &reuse2); + ASSERT(reuse2 > reuse1, "pool reuse works"); + + queue_free(q); memory_pool_destroy(pool); uasync_destroy(ua, 0); + PASS(); } -/* Test 16: Memory pool vs malloc performance comparison */ -static void test_memory_pool_vs_malloc_performance() { - TEST_START("Memory pool vs malloc performance comparison"); - - struct UASYNC* ua = uasync_create(); - - /* Create memory pool (должен вмещать struct ll_entry + test_data_t) */ - struct memory_pool* pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(test_data_t)); - ASSERT_NOT_NULL(pool, "Failed to create memory pool"); - - /* Create queue */ - struct ll_queue* q = queue_new(ua, 0); - ASSERT_NOT_NULL(q, "Failed to create queue"); - - const int iterations = 5000; - - /* Benchmark pool allocation */ - double pool_start = get_time_ms(); - - for (int i = 0; i < iterations; i++) { - test_data_t* data = (test_data_t*)queue_data_new_from_pool(pool); - if (data) { - data->id = i; - data->value = i * 10; - data->checksum = compute_checksum(data); - queue_data_put(q, data, data->id); - queue_data_free((test_data_t*)queue_data_get(q)); - } - } - - double pool_time = get_time_ms() - pool_start; - - /* Benchmark malloc allocation */ - double malloc_start = get_time_ms(); - - for (int i = 0; i < iterations; i++) { - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - if (data) { - data->id = i; - data->value = i * 10; - data->checksum = compute_checksum(data); - queue_data_put(q, data, data->id); - queue_data_free((test_data_t*)queue_data_get(q)); +static void test_stress(void) { + TEST("stress 10k ops"); + double start = now_ms(); + struct UASYNC *ua = uasync_create(); + struct ll_queue *q = queue_new(ua, 64); + + for (int i = 0; i < 10000; i++) { + if (queue_entry_count(q) > 80) { + queue_data_free(queue_data_get(q)); } + test_data_t *d = queue_data_new(sizeof(*d)); + d->id = rand() % 10000; + d->checksum = checksum(d); + queue_data_put(q, d, d->id); + stats.ops++; } - - double malloc_time = get_time_ms() - malloc_start; - - /* Performance comparison */ - printf("\n Pool time: %.3f ms (%.1f ops/sec)", pool_time, iterations * 1000.0 / pool_time); - printf("\n Malloc time: %.3f ms (%.1f ops/sec)", malloc_time, iterations * 1000.0 / malloc_time); - - if (pool_time < malloc_time) { - double speedup = malloc_time / pool_time; - printf("\n Pool is %.2fx faster than malloc", speedup); - } else { - double slowdown = pool_time / malloc_time; - printf("\n Pool is %.2fx slower than malloc", slowdown); - } - - /* Pool should generally be faster or comparable */ - ASSERT_TRUE(pool_time <= malloc_time * 1.5, "Pool should not be significantly slower than malloc"); - - /* Check final pool stats */ - size_t final_allocations = 0, final_reuse = 0; - memory_pool_get_stats(pool, &final_allocations, &final_reuse); - - printf("\n Pool stats: allocations=%zu, reuse=%zu", final_allocations, final_reuse); - ASSERT_GT(final_reuse, iterations * 0.8, "High reuse rate expected"); - - queue_free(q); - memory_pool_destroy(pool); - uasync_destroy(ua, 0); - - TEST_PASS(); -} + while (queue_entry_count(q)) queue_data_free(queue_data_get(q)); -/* Test 14: Reference counting validation */ -static void test_reference_counting() { - TEST_START("Reference counting validation"); - - struct UASYNC* ua = uasync_create(); - struct ll_queue* q = queue_new(ua, 16); /* With hash table for ID lookup */ - - /* Create data and put it in queue */ - test_data_t* data = (test_data_t*)queue_data_new(sizeof(test_data_t)); - data->id = 1; - data->value = 42; - data->checksum = compute_checksum(data); - - /* Get the ll_entry pointer for inspection */ - struct ll_entry* entry = (struct ll_entry*)((char*)data - sizeof(struct ll_entry)); - ASSERT_EQ(entry->ref_count, 1, "New entry should have ref_count = 1"); - - /* Put in queue - ref_count НЕ изменяется в новой архитектуре */ - ASSERT_EQ(queue_data_put(q, data, data->id), 0, "Failed to put data"); - ASSERT_EQ(entry->ref_count, 1, "Entry in queue should still have ref_count = 1 (new architecture)"); - - /* Find by ID - should not change ref_count */ - test_data_t* found = (test_data_t*)queue_find_data_by_id(q, 1); - ASSERT_NOT_NULL(found, "Should find data"); - ASSERT_EQ(entry->ref_count, 1, "Find should not change ref_count"); - - /* Get from queue - ref_count НЕ изменяется в новой архитектуре */ - test_data_t* retrieved = (test_data_t*)queue_data_get(q); - ASSERT_NOT_NULL(retrieved, "Should retrieve data"); - ASSERT_EQ(entry->ref_count, 1, "Get should not change ref_count (new architecture)"); - - /* Free data - should decrement to 0 and free memory */ - queue_data_free(retrieved); - /* Can't check ref_count after free */ - - queue_free(q); - uasync_destroy(ua, 0); - - TEST_PASS(); + stats.time_ms += now_ms() - start; + queue_free(q); uasync_destroy(ua, 0); + PASS(); } -/* Main test runner */ -int main() { - printf("=== ll_queue Comprehensive Tests with Current API ===\n"); - printf("Starting tests...\n\n"); - - /* Initialize debug system */ +/* ------------------------------------------------------------------ */ +int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); debug_set_categories(DEBUG_CATEGORY_LL_QUEUE); - - double suite_start_time = get_time_ms(); - - /* Run all tests */ - test_basic_creation(); - test_data_operations(); - test_fifo_ordering(); - test_lifo_operations(); - test_callback_basic(); - test_callback_suspension(); - test_waiter_basic(); - test_waiter_cancellation(); - test_size_limits(); - test_hash_table_operations(); - test_memory_pool_integration(); - test_edge_cases(); - test_stress_high_volume(); - test_reference_counting(); - test_memory_pool_stress(); - test_memory_pool_vs_malloc_performance(); - - double suite_end_time = get_time_ms(); - - printf("\n=== Test Suite Summary ===\n"); - printf("Tests run: %d\n", test_stats.tests_run); - printf("Tests passed: %d\n", test_stats.tests_passed); - printf("Tests failed: %d\n", test_stats.tests_failed); - printf("\nError Statistics:\n"); - printf(" Memory errors: %d\n", test_stats.memory_errors); - printf(" Hash table errors: %d\n", test_stats.hash_table_errors); - printf(" Reference count errors: %d\n", test_stats.ref_count_errors); - printf("\nPerformance:\n"); - printf(" Total operations: %ld\n", test_stats.total_operations); - printf(" Total time: %.2f ms\n", test_stats.total_time_ms); - if (test_stats.total_time_ms > 0) { - printf(" Average ops/sec: %.2f\n", test_stats.total_operations * 1000.0 / test_stats.total_time_ms); - } - printf("\nCallback Statistics:\n"); - printf(" Queue callbacks: %d\n", test_stats.queue_callback_count); - printf(" Waiter callbacks: %d\n", test_stats.waiter_callback_count); - - if (test_stats.tests_failed == 0 && test_stats.memory_errors == 0 && - test_stats.hash_table_errors == 0 && test_stats.ref_count_errors == 0) { - printf("\n✅ ALL TESTS PASSED - ll_queue implementation is robust!\n"); - return 0; - } else { - printf("\n❌ TEST FAILURES DETECTED - Review implementation\n"); - return 1; - } + + double t0 = now_ms(); + + test_basic(); + test_fifo(); + test_lifo_priority(); + test_callback(); + test_waiter(); + test_limits_hash(); + test_pool(); + test_stress(); + + printf("\n=== SUMMARY ===\n" + "run=%d passed=%d failed=%d\n" + "callbacks: queue=%d waiter=%d\n" + "ops=%ld time=%.2f ms\n", + stats.run, stats.passed, stats.failed, + stats.cb_queue, stats.cb_waiter, stats.ops, now_ms()-t0); + + return stats.failed ? 1 : 0; } \ No newline at end of file