You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
569 lines
20 KiB
569 lines
20 KiB
// test_tcp_io.c — тесты библиотеки tcp_conn |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <assert.h> |
|
#include <errno.h> |
|
#include "../lib/platform_compat.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/tcp_io.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
|
|
#ifndef MSG_NOSIGNAL |
|
#define MSG_NOSIGNAL 0 |
|
#endif |
|
|
|
static int tests_run = 0; |
|
static int tests_passed = 0; |
|
static int tests_failed = 0; |
|
|
|
#define TEST_START(name) do { DEBUG_INFO(DEBUG_CATEGORY_SYS, "TEST: %s... ", name); tests_run++; } while(0) |
|
#define TEST_PASS() do { DEBUG_INFO(DEBUG_CATEGORY_SYS, "PASS"); tests_passed++; } while(0) |
|
#define TEST_FAIL(msg) do { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "FAIL: %s", msg); tests_failed++; return; } while(0) |
|
#define ASSERT_TRUE(cond, msg) do { if (!(cond)) TEST_FAIL(msg); } while(0) |
|
#define ASSERT_EQ(a, b, msg) do { if ((a) != (b)) TEST_FAIL(msg); } while(0) |
|
|
|
static int g_fin_count = 0; |
|
static int g_error_count = 0; |
|
static int g_last_error = 0; |
|
static struct tcp_conn* g_last_fin_tc = NULL; |
|
static int g_fin_sent_count = 0; |
|
static int g_closed_count = 0; |
|
static int g_last_event = 0; // 1=on_fin_sent, 2=on_closed — проверка порядка |
|
|
|
static void on_fin(struct tcp_conn* tc, void* arg) { |
|
(void)arg; |
|
g_fin_count++; |
|
g_last_fin_tc = tc; |
|
} |
|
|
|
static void on_error(struct tcp_conn* tc, int err, void* arg) { |
|
(void)arg; |
|
g_error_count++; |
|
g_last_error = err; |
|
} |
|
|
|
static void on_fin_sent_cb(struct tcp_conn* tc, void* arg) { |
|
(void)tc; (void)arg; |
|
g_fin_sent_count++; g_last_event = 1; |
|
} |
|
|
|
static void on_closed_cb(struct tcp_conn* tc, void* arg) { |
|
(void)tc; (void)arg; |
|
g_closed_count++; g_last_event = 2; |
|
} |
|
|
|
static void reset_counters(void) { |
|
g_fin_count = 0; |
|
g_error_count = 0; |
|
g_last_error = 0; |
|
g_last_fin_tc = NULL; |
|
g_fin_sent_count = 0; |
|
g_closed_count = 0; |
|
g_last_event = 0; |
|
} |
|
|
|
static int push_write(struct tcp_conn* tc, const uint8_t* data, size_t len) { |
|
size_t offset = 0; |
|
while (offset < len) { |
|
size_t chunk = len - offset; |
|
if (chunk > tc->write_chunk_size) chunk = tc->write_chunk_size; |
|
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool); |
|
uint8_t* buf = memory_pool_alloc(tc->data_pool); |
|
if (!e || !buf) { if (e) queue_entry_free(e); if (buf) memory_pool_free(tc->data_pool, buf); return -1; } |
|
memcpy(buf, data + offset, chunk); |
|
e->dgram = buf; e->len = (uint16_t)chunk; |
|
queue_data_put(tc->write_queue, e); |
|
offset += chunk; |
|
} |
|
return 0; |
|
} |
|
|
|
static void test_basic_send_recv(void) { |
|
TEST_START("Basic send and recv via tcp_conn"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
// push_write отправляет данные синхронно через write_queue_fetch_cb |
|
uint8_t send_buf[3000]; |
|
memset(send_buf, 'A', sizeof(send_buf)); |
|
int ret = push_write(tc, send_buf, sizeof(send_buf)); |
|
ASSERT_EQ(ret, 0, "push_write first call failed"); |
|
|
|
// Читаем с другой стороны |
|
uint8_t recv_buf[4096] = {0}; |
|
ssize_t total = 0; |
|
while (total < (ssize_t)sizeof(send_buf)) { |
|
ssize_t n = recv(sv[1], recv_buf + total, sizeof(recv_buf) - total, 0); |
|
ASSERT_TRUE(n >= 0, "recv failed on peer socket"); |
|
total += n; |
|
} |
|
ASSERT_TRUE(memcmp(send_buf, recv_buf, sizeof(send_buf)) == 0, "received data mismatch"); |
|
|
|
// Отправляем с другой стороны ДО poll — данные попадут в буфер ядра, |
|
// и при EPOLLHUP handle_error вычитает их дренажом в read_queue |
|
uint8_t peer_data[500]; |
|
memset(peer_data, 'B', sizeof(peer_data)); |
|
ssize_t wret = send(sv[1], peer_data, sizeof(peer_data), MSG_NOSIGNAL); |
|
ASSERT_TRUE(wret == sizeof(peer_data), "send on peer socket failed"); |
|
uasync_poll(ua, 10); |
|
|
|
struct ll_entry* e = queue_data_get(tc->read_queue); |
|
ASSERT_TRUE(e != NULL, "no data in read_queue"); |
|
ASSERT_EQ(e->len, (int)sizeof(peer_data), "read_queue entry length mismatch"); |
|
ASSERT_TRUE(memcmp(e->dgram, peer_data, sizeof(peer_data)) == 0, "read_queue data mismatch"); |
|
queue_entry_free(e); |
|
queue_resume_callback(tc->read_queue); |
|
|
|
// Закрываем peer — EPOLLHUP обработан через error_cb (handle_error) |
|
close(sv[1]); |
|
uasync_poll(ua, 10); |
|
ASSERT_EQ(g_error_count, 1, "error_cb not called on peer close"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[0]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_partial_write(void) { |
|
TEST_START("Partial write recovery"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 4096, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
// Заполняем приёмный буфер sv[1] маленькими чтениями, чтобы создать EAGAIN на sv[0] |
|
// Отправляем много данных быстро, чтобы сокет заполнился |
|
int send_size = 256; |
|
uint8_t* big_buf = u_malloc(200000); |
|
ASSERT_TRUE(big_buf != NULL, "malloc failed"); |
|
memset(big_buf, 'X', 200000); |
|
|
|
int ret = push_write(tc, big_buf, 200000); |
|
ASSERT_EQ(ret, 0, "push_write large failed"); |
|
uasync_poll(ua, 1); // write_cb отправляет что может |
|
|
|
// Дрейним sv[1] и проверяем что все данные приходят |
|
uint8_t read_buf[8192]; |
|
ssize_t got = 0; |
|
int iterations = 0; |
|
while (got < 200000 && iterations < 1000) { |
|
uasync_poll(ua, 1); // write_cb может дослать остаток |
|
ssize_t n = recv(sv[1], read_buf, sizeof(read_buf), 0); |
|
if (n > 0) { got += n; iterations = 0; continue; } |
|
iterations++; |
|
} |
|
ASSERT_TRUE(got == 200000, "not all data received"); |
|
|
|
u_free(big_buf); |
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_high_water_pause(void) { |
|
TEST_START("Read high-water pause and resume"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
// hw=2, lw=0 — пауза после 2 блоков, resume когда пусто |
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 64, 8192, 2, 0, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
// Шлём 5 блоков по 64 байта с другой стороны |
|
for (int i = 0; i < 5; i++) { |
|
uint8_t buf[64]; |
|
memset(buf, (uint8_t)i, sizeof(buf)); |
|
ssize_t wret = send(sv[1], buf, sizeof(buf), MSG_NOSIGNAL); |
|
ASSERT_TRUE(wret == sizeof(buf), "send failed on iteration"); |
|
} |
|
// Крутим poll пока не упрёмся в high_water (нужно несколько poll-итераций) |
|
for (int i = 0; i < 20; i++) { |
|
uasync_poll(ua, 1); |
|
if (tc->read_paused) break; |
|
} |
|
ASSERT_TRUE(tc->read_paused == 1, "read not paused at high water"); |
|
|
|
// Дрейним read_queue — после lw=0 должен сработать resume |
|
int drained = 0; |
|
struct ll_entry* e; |
|
while ((e = queue_data_get(tc->read_queue)) != NULL) { |
|
drained++; |
|
queue_entry_free(e); |
|
queue_resume_callback(tc->read_queue); |
|
} |
|
ASSERT_TRUE(drained >= 2, "should have at least 2 entries"); |
|
|
|
uasync_poll(ua, 10); |
|
ASSERT_TRUE(tc->read_queue->count > 0, "read not resumed"); |
|
|
|
// Дрейним остаток |
|
while ((e = queue_data_get(tc->read_queue)) != NULL) { |
|
queue_entry_free(e); |
|
queue_resume_callback(tc->read_queue); |
|
} |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_connect_detection(void) { |
|
TEST_START("Connect completion detection"); |
|
#ifdef _WIN32 |
|
TEST_PASS(); |
|
return; |
|
#else |
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int listen_fd = socket(AF_INET, SOCK_STREAM, 0); |
|
ASSERT_TRUE(listen_fd >= 0, "socket failed"); |
|
|
|
struct sockaddr_in addr; |
|
memset(&addr, 0, sizeof(addr)); |
|
addr.sin_family = AF_INET; |
|
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
|
addr.sin_port = 0; |
|
ASSERT_EQ(bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)), 0, "bind failed"); |
|
|
|
socklen_t alen = sizeof(addr); |
|
getsockname(listen_fd, (struct sockaddr*)&addr, &alen); |
|
ASSERT_EQ(listen(listen_fd, 1), 0, "listen failed"); |
|
|
|
int client_fd = socket(AF_INET, SOCK_STREAM, 0); |
|
ASSERT_TRUE(client_fd >= 0, "socket failed"); |
|
fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, client_fd, 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
ASSERT_EQ(tc->connected, 0, "should not be connected yet"); |
|
|
|
int ret = connect(client_fd, (struct sockaddr*)&addr, sizeof(addr)); |
|
ASSERT_TRUE(ret < 0 && errno == EINPROGRESS, "connect should return EINPROGRESS"); |
|
|
|
int server_fd = accept(listen_fd, NULL, NULL); |
|
ASSERT_TRUE(server_fd >= 0, "accept failed"); |
|
|
|
uasync_poll(ua, 100); |
|
ASSERT_EQ(tc->connected, 1, "connect not detected"); |
|
|
|
close(server_fd); |
|
tcp_conn_destroy(tc); |
|
close(listen_fd); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
#endif |
|
} |
|
|
|
static void test_error_callback(void) { |
|
TEST_START("Error callback on broken socket"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
|
|
close(sv[1]); |
|
uasync_poll(ua, 10); |
|
ASSERT_EQ(tc->error, 1, "error not set on broken connection"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[0]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
// ==================================================================== |
|
// FIN / Close через очередь — новые тесты |
|
// ==================================================================== |
|
|
|
static void test_push_fin(void) { |
|
TEST_START("push_fin via write_queue"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
uint8_t data[64]; |
|
memset(data, 'D', sizeof(data)); |
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed"); |
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin failed"); |
|
|
|
uasync_poll(ua, 10); |
|
|
|
uint8_t recv_buf[128] = {0}; |
|
ssize_t total = 0; |
|
while (total < (ssize_t)sizeof(data)) { |
|
ssize_t n = recv(sv[1], recv_buf + total, sizeof(recv_buf) - total, 0); |
|
ASSERT_TRUE(n >= 0, "recv failed on peer"); |
|
total += n; |
|
} |
|
ASSERT_EQ(total, (ssize_t)sizeof(data), "should receive all data before EOF"); |
|
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch"); |
|
|
|
// После всех данных — EOF (FIN) |
|
ssize_t n = recv(sv[1], recv_buf, sizeof(recv_buf), 0); |
|
ASSERT_EQ(n, 0, "should get EOF after FIN"); |
|
|
|
ASSERT_EQ(tc->fin_local, 1, "tc->fin_local not set"); |
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called"); |
|
ASSERT_EQ(g_closed_count, 0, "on_closed should not be called"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_push_close(void) { |
|
TEST_START("push_close via write_queue"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_closed = on_closed_cb; |
|
|
|
uint8_t data[64]; |
|
memset(data, 'E', sizeof(data)); |
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write failed"); |
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close failed"); |
|
|
|
uasync_poll(ua, 10); |
|
|
|
// Peer должен получить данные перед закрытием |
|
uint8_t recv_buf[128] = {0}; |
|
ssize_t total = recv(sv[1], recv_buf, sizeof(recv_buf), 0); |
|
ASSERT_TRUE(total == sizeof(data), "peer should receive data"); |
|
ASSERT_TRUE(memcmp(data, recv_buf, sizeof(data)) == 0, "data mismatch"); |
|
|
|
ASSERT_EQ(tc->closed, 1, "tc->closed not set"); |
|
ASSERT_EQ(tc->sock, SOCKET_INVALID, "sock should be invalid after close"); |
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_fin_data_ordering(void) { |
|
TEST_START("Data before FIN ordering"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
uint8_t data1[64], data2[32], data3[48]; |
|
memset(data1, 0x01, sizeof(data1)); memset(data2, 0x02, sizeof(data2)); memset(data3, 0x03, sizeof(data3)); |
|
ASSERT_EQ(push_write(tc, data1, sizeof(data1)), 0, "push_write data1"); |
|
ASSERT_EQ(push_write(tc, data2, sizeof(data2)), 0, "push_write data2"); |
|
ASSERT_EQ(push_write(tc, data3, sizeof(data3)), 0, "push_write data3"); |
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin"); |
|
|
|
uasync_poll(ua, 10); |
|
|
|
// TCP — поток, блоки могут склеиться. Читаем всё и сверяем суммарно. |
|
uint8_t buf[256], ref[sizeof(data1)+sizeof(data2)+sizeof(data3)]; |
|
memcpy(ref, data1, sizeof(data1)); memcpy(ref + sizeof(data1), data2, sizeof(data2)); memcpy(ref + sizeof(data1) + sizeof(data2), data3, sizeof(data3)); |
|
ssize_t total = 0; |
|
while (total < (ssize_t)sizeof(ref)) { |
|
ssize_t n = recv(sv[1], buf + total, sizeof(buf) - total, 0); |
|
ASSERT_TRUE(n > 0, "recv failed on peer"); |
|
total += n; |
|
} |
|
ASSERT_EQ(total, (ssize_t)sizeof(ref), "total received mismatch"); |
|
ASSERT_TRUE(memcmp(buf, ref, sizeof(ref)) == 0, "data content mismatch"); |
|
|
|
// EOF |
|
ssize_t n = recv(sv[1], buf, sizeof(buf), 0); |
|
ASSERT_EQ(n, 0, "should get EOF after data"); |
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent should be called"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_double_push_fin(void) { |
|
TEST_START("Double push_fin ignored"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_fin_sent = on_fin_sent_cb; |
|
|
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "first push_fin"); |
|
ASSERT_EQ(tcp_conn_push_fin(tc), -1, "second push_fin should return -1"); |
|
|
|
uasync_poll(ua, 10); |
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent called more than once"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_double_push_close(void) { |
|
TEST_START("Double push_close ignored"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_closed = on_closed_cb; |
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "first push_close"); |
|
uasync_poll(ua, 10); |
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called after first close"); |
|
|
|
ASSERT_EQ(tcp_conn_push_close(tc), -1, "second push_close should return -1"); |
|
ASSERT_EQ(g_closed_count, 1, "on_closed called more than once"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
static void test_fin_before_close(void) { |
|
TEST_START("FIN before close ordering"); |
|
|
|
uasync_t* ua = uasync_create(); |
|
ASSERT_TRUE(ua != NULL, "uasync_create failed"); |
|
reset_counters(); |
|
|
|
int sv[2]; |
|
ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0, "socketpair failed"); |
|
for (int i = 0; i < 2; i++) fcntl(sv[i], F_SETFL, fcntl(sv[i], F_GETFL, 0) | O_NONBLOCK); |
|
|
|
struct tcp_conn* tc = tcp_conn_create(ua, sv[0], 1500, 8192, 32, 8, 0, on_fin, on_error, NULL); |
|
ASSERT_TRUE(tc != NULL, "tcp_conn_create failed"); |
|
tc->on_fin_sent = on_fin_sent_cb; |
|
tc->on_closed = on_closed_cb; |
|
|
|
uint8_t data[32]; |
|
memset(data, 'F', sizeof(data)); |
|
ASSERT_EQ(push_write(tc, data, sizeof(data)), 0, "push_write"); |
|
ASSERT_EQ(tcp_conn_push_fin(tc), 0, "push_fin"); |
|
ASSERT_EQ(tcp_conn_push_close(tc), 0, "push_close"); |
|
|
|
uasync_poll(ua, 10); |
|
|
|
// Peer получает данные |
|
uint8_t buf[64]; |
|
ssize_t n = recv(sv[1], buf, sizeof(buf), 0); |
|
ASSERT_EQ(n, (ssize_t)sizeof(data), "peer should get data"); |
|
|
|
ASSERT_EQ(g_fin_sent_count, 1, "on_fin_sent not called"); |
|
ASSERT_EQ(g_closed_count, 1, "on_closed not called"); |
|
|
|
tcp_conn_destroy(tc); |
|
close(sv[1]); |
|
uasync_destroy(ua, 0); |
|
TEST_PASS(); |
|
} |
|
|
|
int main(void) { |
|
#ifdef _WIN32 |
|
printf("[SKIP] test_tcp_io — socketpair() not available on Windows\n"); |
|
return 0; |
|
#else |
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_INFO); |
|
debug_set_categories(DEBUG_CATEGORY_ALL); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== tcp_io Unit Tests ==="); |
|
|
|
test_basic_send_recv(); |
|
test_partial_write(); |
|
test_high_water_pause(); |
|
test_connect_detection(); |
|
test_error_callback(); |
|
test_push_fin(); |
|
test_push_close(); |
|
test_fin_data_ordering(); |
|
test_double_push_fin(); |
|
test_double_push_close(); |
|
test_fin_before_close(); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "=== Test Statistics ==="); |
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Tests run: %d", tests_run); |
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Tests passed: %d", tests_passed); |
|
DEBUG_INFO(DEBUG_CATEGORY_SYS, "Tests failed: %d", tests_failed); |
|
|
|
return (tests_failed > 0) ? 1 : 0; |
|
#endif |
|
}
|
|
|