test(websocket): 添加 WebSocket 协议单元测试(12 项)
覆盖内容: - 帧解析:文本/二进制/关闭/空负载、掩码解掩码、16位/64位长度编码 - 帧编码:文本帧、关闭帧(含 code+reason)、ping/pong 往返 - 握手验证:Sec-WebSocket-Accept 计算正确性 - 粘包场景:多帧连续解析 - 边界条件:不完整数据、空负载、64KB 大负载 - 注册表:初始连接数为 0 使用 socketpair 创建内部通信管道,无需真实网络。
This commit is contained in:
parent
05cc458440
commit
faedbd55a8
4
Makefile
4
Makefile
@ -95,8 +95,8 @@ $(UNIT_TEST_DIR)/test_http: $(UNIT_TEST_DIR)/test_http.c http.c log.c $(UNITY_SR
|
|||||||
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c $(UNITY_SRC)
|
$(UNIT_TEST_DIR)/test_static: $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c $(UNITY_SRC)
|
||||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c $(UNITY_SRC) $(LDFLAGS)
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c tls.c access_log.c platform.c $(UNITY_SRC) $(LDFLAGS)
|
||||||
|
|
||||||
$(UNIT_TEST_DIR)/test_log: $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC)
|
$(UNIT_TEST_DIR)/test_websocket: $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC)
|
||||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_websocket.c websocket.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||||
|
|
||||||
$(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC)
|
$(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC)
|
||||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC) -lm
|
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c access_log.c http.c $(UNITY_SRC) -lm
|
||||||
|
|||||||
254
tests/unit/test_websocket.c
Normal file
254
tests/unit/test_websocket.c
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
/**
|
||||||
|
* @file test_websocket.c
|
||||||
|
* @brief WebSocket 协议单元测试
|
||||||
|
*
|
||||||
|
* 覆盖帧解析、帧编码、握手、广播注册表等核心逻辑。
|
||||||
|
* 使用 socketpair 创建内部通信管道,避免真实网络依赖。
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "unity.h"
|
||||||
|
#include "websocket.h"
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static int client_fd, server_fd;
|
||||||
|
|
||||||
|
void setUp(void) {
|
||||||
|
int fds[2];
|
||||||
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0) {
|
||||||
|
TEST_FAIL_MESSAGE("socketpair 失败");
|
||||||
|
}
|
||||||
|
client_fd = fds[0];
|
||||||
|
server_fd = fds[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void tearDown(void) {
|
||||||
|
close(client_fd);
|
||||||
|
close(server_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 基本文本帧(无掩码) */
|
||||||
|
void test_parse_text_frame(void) {
|
||||||
|
uint8_t data[] = { 0x81, 0x05, 'H', 'e', 'l', 'l', 'o' };
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data, sizeof(data), &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(7, consumed);
|
||||||
|
TEST_ASSERT_TRUE(frame.fin);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_TEXT, frame.opcode);
|
||||||
|
TEST_ASSERT_FALSE(frame.masked);
|
||||||
|
TEST_ASSERT_EQUAL_INT(5, frame.payload_len);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Hello", (char *)frame.payload);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 掩码帧(客户端发来的帧) */
|
||||||
|
void test_parse_masked_frame(void) {
|
||||||
|
/* mask_key = {0x12, 0x34, 0x56, 0x78}
|
||||||
|
* payload = XOR(0x41, 0x12) = 0x53 'S'
|
||||||
|
* = XOR(0x59, 0x34) = 0x6D 'm'
|
||||||
|
* = XOR(0x32, 0x56) = 0x64 'd'
|
||||||
|
* = XOR(0x0C, 0x78) = 0x74 't'
|
||||||
|
* = XOR(0x5D, 0x12) = 0x4F 'O'
|
||||||
|
*/
|
||||||
|
uint8_t data[] = { 0x81, 0x85, 0x12, 0x34, 0x56, 0x78,
|
||||||
|
0x41, 0x59, 0x32, 0x0C, 0x5D };
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data, sizeof(data), &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(11, consumed);
|
||||||
|
TEST_ASSERT_TRUE(frame.masked);
|
||||||
|
TEST_ASSERT_EQUAL_INT(5, frame.payload_len);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("SmdtO", (char *)frame.payload);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 数据不完整应返回 -1 */
|
||||||
|
void test_parse_incomplete_frame(void) {
|
||||||
|
uint8_t data[] = { 0x81, 0x05, 'H' }; /* 需要 5 字节 payload,只有 1 字节 */
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, ws_parse_frame(data, sizeof(data), &frame, &consumed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 16 位扩展长度(126 ~ 65535) */
|
||||||
|
void test_parse_16bit_length(void) {
|
||||||
|
uint8_t payload[130];
|
||||||
|
memset(payload, 'A', 130);
|
||||||
|
uint8_t data[134] = { 0x82, 0x7E, 0x00, 0x82 }; /* BINARY, 长度=130 */
|
||||||
|
memcpy(data + 4, payload, 130);
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data, sizeof(data), &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(134, consumed);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_BINARY, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(130, frame.payload_len);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, memcmp(frame.payload, payload, 130));
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_send_frame + ws_parse_frame 往返(文本帧) */
|
||||||
|
void test_send_parse_roundtrip_text(void) {
|
||||||
|
const char *text = "Cocoon WebSocket";
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_send_text(server_fd, text));
|
||||||
|
|
||||||
|
uint8_t buf[64];
|
||||||
|
ssize_t n = recv(client_fd, buf, sizeof(buf), 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(buf, n, &frame, &consumed));
|
||||||
|
TEST_ASSERT_TRUE(frame.fin);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_TEXT, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(strlen(text), frame.payload_len);
|
||||||
|
TEST_ASSERT_EQUAL_STRING(text, (char *)frame.payload);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_send_close — 带关闭码和原因 */
|
||||||
|
void test_send_close_with_reason(void) {
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_send_close(server_fd, 1000, "bye"));
|
||||||
|
uint8_t buf[32];
|
||||||
|
ssize_t n = recv(client_fd, buf, sizeof(buf), 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(buf, n, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_CLOSE, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(5, frame.payload_len); /* 2 字节 code + 3 字节 "bye" */
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_send_ping / ws_send_pong */
|
||||||
|
void test_send_ping_pong(void) {
|
||||||
|
/* Ping 帧(无负载) */
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_send_ping(server_fd));
|
||||||
|
uint8_t buf[16];
|
||||||
|
ssize_t n = recv(client_fd, buf, sizeof(buf), 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(buf, n, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_PING, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, frame.payload_len);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
|
||||||
|
/* Pong 帧(带负载) */
|
||||||
|
uint8_t payload[] = { 0x01, 0x02, 0x03 };
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_send_pong(server_fd, payload, 3));
|
||||||
|
n = recv(client_fd, buf, sizeof(buf), 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(buf, n, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_PONG, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(3, frame.payload_len);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_handshake — 验证响应头和 Sec-WebSocket-Accept */
|
||||||
|
void test_handshake_accept(void) {
|
||||||
|
const char *key = "dGhlIHNhbXBsZSBub25jZQ==";
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_handshake(server_fd, key));
|
||||||
|
|
||||||
|
char buf[512];
|
||||||
|
ssize_t n = recv(client_fd, buf, sizeof(buf) - 1, 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
buf[n] = '\0';
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(strstr(buf, "HTTP/1.1 101 Switching Protocols"));
|
||||||
|
TEST_ASSERT_NOT_NULL(strstr(buf, "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo="));
|
||||||
|
TEST_ASSERT_NOT_NULL(strstr(buf, "Upgrade: websocket"));
|
||||||
|
TEST_ASSERT_NOT_NULL(strstr(buf, "Connection: Upgrade"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_connection_count — 初始为 0 */
|
||||||
|
void test_connection_count_initial(void) {
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_connection_count());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_send_frame 大负载(64 位长度编码,>65535) */
|
||||||
|
void test_send_64bit_length(void) {
|
||||||
|
uint8_t *payload = (uint8_t *)malloc(65536);
|
||||||
|
TEST_ASSERT_NOT_NULL(payload);
|
||||||
|
memset(payload, 0xAB, 65536);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_send_frame(server_fd, WS_OP_BINARY, payload, 65536));
|
||||||
|
|
||||||
|
uint8_t *buf = (uint8_t *)malloc(65546);
|
||||||
|
TEST_ASSERT_NOT_NULL(buf);
|
||||||
|
ssize_t total = 0;
|
||||||
|
while (total < 65546) {
|
||||||
|
ssize_t n = recv(client_fd, buf + total, 65546 - total, 0);
|
||||||
|
TEST_ASSERT_GREATER_THAN_INT(0, n);
|
||||||
|
total += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(buf, total, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_BINARY, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(65536, frame.payload_len);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, memcmp(frame.payload, payload, 65536));
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
free(payload);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 多帧连续解析(模拟粘包) */
|
||||||
|
void test_parse_multiple_frames(void) {
|
||||||
|
uint8_t frame1[] = { 0x81, 0x03, 'a', 'b', 'c' }; /* 文本 "abc" */
|
||||||
|
uint8_t frame2[] = { 0x82, 0x02, 0x01, 0x02 }; /* 二进制 2 字节 */
|
||||||
|
uint8_t data[16];
|
||||||
|
memcpy(data, frame1, 5);
|
||||||
|
memcpy(data + 5, frame2, 4);
|
||||||
|
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
size_t offset = 0;
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data + offset, 9 - offset, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(5, consumed);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_TEXT, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("abc", (char *)frame.payload);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
|
||||||
|
offset = 5;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data + offset, 9 - offset, &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(4, consumed);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_BINARY, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(2, frame.payload_len);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 测试 ws_parse_frame — 空负载帧 */
|
||||||
|
void test_parse_empty_payload(void) {
|
||||||
|
uint8_t data[] = { 0x88, 0x00 }; /* close 帧,无负载 */
|
||||||
|
ws_frame_t frame = {0};
|
||||||
|
size_t consumed = 0;
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, ws_parse_frame(data, sizeof(data), &frame, &consumed));
|
||||||
|
TEST_ASSERT_EQUAL_INT(2, consumed);
|
||||||
|
TEST_ASSERT_EQUAL_INT(WS_OP_CLOSE, frame.opcode);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, frame.payload_len);
|
||||||
|
TEST_ASSERT_NULL(frame.payload);
|
||||||
|
ws_frame_free(&frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_parse_text_frame);
|
||||||
|
RUN_TEST(test_parse_masked_frame);
|
||||||
|
RUN_TEST(test_parse_incomplete_frame);
|
||||||
|
RUN_TEST(test_parse_16bit_length);
|
||||||
|
RUN_TEST(test_send_parse_roundtrip_text);
|
||||||
|
RUN_TEST(test_send_close_with_reason);
|
||||||
|
RUN_TEST(test_send_ping_pong);
|
||||||
|
RUN_TEST(test_handshake_accept);
|
||||||
|
RUN_TEST(test_connection_count_initial);
|
||||||
|
RUN_TEST(test_send_64bit_length);
|
||||||
|
RUN_TEST(test_parse_multiple_frames);
|
||||||
|
RUN_TEST(test_parse_empty_payload);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user