/** * Компактные, логичные unit-тесты для ll_queue (embedded ll_entry + xxx=0) */ #include #include #include #include #include #include #include #include "../lib/ll_queue.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/memory_pool.h" #ifndef DEBUG_CATEGORY_LL_QUEUE #define DEBUG_CATEGORY_LL_QUEUE 1 #endif static struct { int run, passed, failed; int cb_queue, cb_waiter; int mem_err, hash_err; long ops; double time_ms; } stats = {0}; #define TEST(name) do { \ printf("TEST: %-35s ", name); fflush(stdout); \ stats.run++; \ } 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(c, m) do { if (!(c)) { FAIL(m); return; } } while(0) #define ASSERT_EQ(a,b,m) ASSERT((a)==(b),m) /* ------------------------------------------------------------------ */ static double now_ms(void) { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0; } 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 checksum(const test_data_t *d) { return (uint64_t)d->id * 31 + (uint64_t)d->value * 17 + strlen(d->name); } /* Callback consumer: забирает и освобождает элемент */ static void queue_cb(struct ll_queue *q, void *arg) { int *cnt = arg; (*cnt)++; stats.cb_queue++; test_data_t *taken = (test_data_t*)queue_data_get(q); ASSERT(checksum(taken) == taken->checksum, "corruption in cb"); queue_entry_free((struct ll_entry*)taken); queue_resume_callback(q); // next item when ready } static void waiter_cb(struct ll_queue *q, void *arg) { (*(int*)arg)++; stats.cb_waiter++; } /* ------------------------------------------------------------------ */ static void test_basic(void) { TEST("basic creation / free"); struct UASYNC *ua = uasync_create(); struct ll_queue *q1 = queue_new(ua, 0,"q1"); struct ll_queue *q2 = queue_new(ua, 16,"q2"); ASSERT(q1 && q2, "queue_new failed"); ASSERT_EQ(queue_entry_count(q1), 0, ""); queue_free(q1); queue_free(q2); uasync_destroy(ua, 0); PASS(); } static void test_fifo(void) { TEST("FIFO ordering"); struct UASYNC *ua = uasync_create(); struct ll_queue *q = queue_new(ua, 0,"q3"); for (int i = 0; i < 10; i++) { test_data_t *d = (test_data_t*)queue_entry_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_with_index(q, (struct ll_entry*)d, 0, 4); } ASSERT_EQ(queue_entry_count(q), 10, ""); for (int i = 0; i < 10; i++) { test_data_t *d = (test_data_t*)queue_data_get(q); ASSERT(d && d->id == i, "FIFO violation"); queue_entry_free((struct ll_entry*)d); } ASSERT_EQ(queue_entry_count(q), 0, ""); queue_free(q); uasync_destroy(ua, 0); PASS(); } static void test_lifo_priority(void) { TEST("put_first (LIFO priority)"); struct UASYNC *ua = uasync_create(); struct ll_queue *q = queue_new(ua, 0,"q4"); for (int i = 0; i < 3; i++) { test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i; d->value = i; d->checksum = checksum(d); queue_data_put_with_index(q, (struct ll_entry*)d, 0, 4); } test_data_t *pri = (test_data_t*)queue_entry_new(sizeof(*pri)); pri->id = 999; pri->value = 999; pri->checksum = checksum(pri); queue_data_put_first_with_index(q, (struct ll_entry*)pri, 0, 4); test_data_t *first = (test_data_t*)queue_data_get(q); ASSERT(first && first->id == 999, "priority first"); queue_entry_free((struct ll_entry*)first); for (int i = 0; i < 3; i++) { test_data_t *d = (test_data_t*)queue_data_get(q); ASSERT(d && d->id == i, "remaining FIFO"); queue_entry_free((struct ll_entry*)d); } queue_free(q); uasync_destroy(ua, 0); PASS(); } static void test_callback(void) { TEST("callback serial processing"); struct UASYNC *ua = uasync_create(); struct ll_queue *q = queue_new(ua, 0,"q5"); int cnt = 0; queue_set_callback(q, queue_cb, &cnt); for (int i = 0; i < 5; i++) { test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i; d->value = i*10; d->checksum = checksum(d); queue_data_put_with_index(q, (struct ll_entry*)d, 0, 4); } 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(); } static void test_waiter(void) { TEST("wait_threshold + cancel"); struct UASYNC *ua = uasync_create(); struct ll_queue *q = queue_new(ua, 0,"q6"); 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 *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i; queue_data_put_with_index(q, (struct ll_entry*)d, 0, 4); } 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_entry_free((struct ll_entry*)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(); } 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,"q7"); queue_set_size_limit(q, 3); for (int i = 0; i < 3; i++) { test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = i*10+1; queue_data_put_with_index(q, (struct ll_entry*)d, 0, 4); } test_data_t *ex = (test_data_t*)queue_entry_new(sizeof(*ex)); ASSERT_EQ(queue_data_put_with_index(q, (struct ll_entry*)ex, 0, 4), -1, "limit reject"); uint32_t hash=21; test_data_t *found = (test_data_t*)queue_find_data_by_index(q, &hash, 4); ASSERT(found && found->id == 21, "hash find"); queue_remove_data(q, (struct ll_entry*)found); ASSERT(queue_find_data_by_index(q, &hash, 4) == NULL, "removed"); while (queue_entry_count(q)) queue_entry_free((struct ll_entry*)queue_data_get(q)); queue_free(q); uasync_destroy(ua, 0); 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,"q8"); size_t alloc1 = 0, reuse1 = 0; memory_pool_get_stats(pool, &alloc1, &reuse1); test_data_t *d1 = (test_data_t*)queue_entry_new_from_pool(pool); d1->id = 1; d1->checksum = checksum(d1); queue_data_put_with_index(q, (struct ll_entry*)d1, 0, 4); test_data_t *d2 = (test_data_t*)queue_entry_new_from_pool(pool); d2->id = 2; d2->checksum = checksum(d2); queue_data_put_with_index(q, (struct ll_entry*)d2, 0, 4); queue_entry_free((struct ll_entry*)queue_data_get(q)); // free d1 back to pool queue_entry_free((struct ll_entry*)queue_data_get(q)); // free d2 back to pool // Now allocate again to trigger reuse test_data_t *d3 = (test_data_t*)queue_entry_new_from_pool(pool); ASSERT(d3 != NULL, "alloc after free failed"); d3->id = 3; d3->checksum = checksum(d3); queue_data_put_with_index(q, (struct ll_entry*)d3, 0, 4); queue_entry_free((struct ll_entry*)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(); } 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,"q9"); for (int i = 0; i < 10000; i++) { if (queue_entry_count(q) > 80) { queue_entry_free((struct ll_entry*)queue_data_get(q)); } test_data_t *d = (test_data_t*)queue_entry_new(sizeof(*d)); d->id = rand() % 10000; d->checksum = checksum(d); queue_data_put_with_index(q, (struct ll_entry*)d, 0, 4); stats.ops++; } while (queue_entry_count(q)) queue_entry_free((struct ll_entry*)queue_data_get(q)); stats.time_ms += now_ms() - start; queue_free(q); uasync_destroy(ua, 0); PASS(); } /* ------------------------------------------------------------------ */ int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_DEBUG); debug_set_categories(DEBUG_CATEGORY_LL_QUEUE); 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; }