- 新增 dashboard.c / dashboard.h,提供自包含 HTML 管理面板 - 复用现有 SSE 基础设施(sse.c/sse.h)实现实时指标推送 - 每 2 秒推送 JSON 指标(uptime/connections/requests/2xx/3xx/4xx/5xx),每 6 秒心跳 - 端点:/_status(HTML 面板)、/_status/events(SSE 指标流) - 将 server.c 中的统计变量改为全局可见,供 dashboard 访问 - 新增 9 个单元测试覆盖 dashboard_handle_request / dashboard_handle_events - 编译零警告,474 单元测试 + 111 集成测试通过 (4 个集成测试失败为既有反向代理环境问题,与本次改动无关)
This commit is contained in:
parent
b889b83a7f
commit
d01a5d7559
10
Makefile
10
Makefile
@ -20,7 +20,7 @@ PREFIX ?= /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
# 源文件
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.c fcgi_handler.c cache.c
|
||||
SRCS = main.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c middleware_ext.c load_balance.c grpc.c http3.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = cocoon
|
||||
|
||||
@ -90,8 +90,8 @@ unit-test: $(UNIT_TEST_BINS)
|
||||
fi
|
||||
|
||||
# 单元测试编译规则
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c $(UNITY_SRC) $(LDFLAGS)
|
||||
$(UNIT_TEST_DIR)/test_server: $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_server.c server.c http.c static.c log.c config.c multipart.c tls.c http2.c access_log.c websocket.c platform.c middleware.c plugin.c proxy.c proxy_tls.c healthcheck.c sse.c fastcgi.c fcgi_handler.c cache.c dashboard.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
$(UNIT_TEST_DIR)/test_multipart: $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_multipart.c multipart.c $(UNITY_SRC) -lm
|
||||
@ -146,6 +146,10 @@ $(UNIT_TEST_DIR)/test_fastcgi: $(UNIT_TEST_DIR)/test_fastcgi.c fastcgi.c log.c $
|
||||
$(UNIT_TEST_DIR)/test_cache: $(UNIT_TEST_DIR)/test_cache.c cache.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_cache.c cache.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
# Dashboard 测试
|
||||
$(UNIT_TEST_DIR)/test_dashboard: $(UNIT_TEST_DIR)/test_dashboard.c dashboard.c sse.c log.c $(UNITY_SRC)
|
||||
$(CC) $(CFLAGS) -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_dashboard.c dashboard.c sse.c log.c $(UNITY_SRC) $(LDFLAGS)
|
||||
|
||||
# 安装
|
||||
install: $(TARGET)
|
||||
install -d $(BINDIR)
|
||||
|
||||
216
dashboard.c
Normal file
216
dashboard.c
Normal file
@ -0,0 +1,216 @@
|
||||
/**
|
||||
* dashboard.c - 内置管理 Dashboard 实现
|
||||
*
|
||||
* 提供 `/_status` HTML 管理页面和 `/_status/events` SSE 实时指标流。
|
||||
* HTML 完全自包含(内联 CSS/JS),不依赖外部文件。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#include "dashboard.h"
|
||||
#include "sse.h"
|
||||
#include "log.h"
|
||||
#include "platform.h"
|
||||
#include "../coco/include/coco.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* 内联的 Dashboard HTML 页面 */
|
||||
static const char DASHBOARD_HTML[] =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html lang=\"zh-CN\">\n"
|
||||
"<head>\n"
|
||||
"<meta charset=\"UTF-8\">\n"
|
||||
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
|
||||
"<title>Cocoon Dashboard</title>\n"
|
||||
"<style>\n"
|
||||
"*{box-sizing:border-box}\n"
|
||||
"body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;"
|
||||
"background:#0f172a;color:#e2e8f0;margin:0;padding:20px}\n"
|
||||
".container{max-width:960px;margin:0 auto}\n"
|
||||
"h1{font-size:26px;margin-bottom:6px;color:#38bdf8}\n"
|
||||
".subtitle{color:#64748b;font-size:13px;margin-bottom:24px}\n"
|
||||
".grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:20px}\n"
|
||||
".card{background:#1e293b;border-radius:10px;padding:18px;border:1px solid #334155}\n"
|
||||
".card h3{margin:0 0 10px 0;font-size:12px;color:#94a3b8;text-transform:uppercase;letter-spacing:.5px}\n"
|
||||
".card .value{font-size:30px;font-weight:700;color:#f1f5f9}\n"
|
||||
".card .detail{font-size:12px;color:#64748b;margin-top:4px}\n"
|
||||
".ok{color:#4ade80}.warn{color:#fbbf24}.err{color:#f87171}\n"
|
||||
".bar{display:flex;height:6px;border-radius:3px;overflow:hidden;margin-top:8px;background:#334155}\n"
|
||||
".bar-seg{height:100%;transition:width .3s}\n"
|
||||
".seg2{background:#4ade80}.seg3{background:#38bdf8}.seg4{background:#fbbf24}.seg5{background:#f87171}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div class=\"container\">\n"
|
||||
"<h1>🪵 Cocoon Dashboard</h1>\n"
|
||||
"<div class=\"subtitle\" id=\"sub\">Connecting...</div>\n"
|
||||
"<div class=\"grid\">\n"
|
||||
"<div class=\"card\"><h3>Uptime</h3><div class=\"value\" id=\"uptime\">--</div></div>\n"
|
||||
"<div class=\"card\"><h3>Active Connections</h3><div class=\"value\" id=\"active\">--</div><div class=\"detail\" id=\"maxconn\"></div></div>\n"
|
||||
"<div class=\"card\"><h3>Total Requests</h3><div class=\"value\" id=\"total\">--</div></div>\n"
|
||||
"<div class=\"card\"><h3>Response Distribution</h3><div class=\"value\" id=\"dist\">--</div>\n"
|
||||
"<div class=\"bar\"><div class=\"bar-seg seg2\" id=\"b2\"></div><div class=\"bar-seg seg3\" id=\"b3\"></div>"
|
||||
"<div class=\"bar-seg seg4\" id=\"b4\"></div><div class=\"bar-seg seg5\" id=\"b5\"></div></div>\n"
|
||||
"</div>\n"
|
||||
"</div>\n"
|
||||
"</div>\n"
|
||||
"<script>\n"
|
||||
"const evt=new EventSource('/_status/events');\n"
|
||||
"evt.onmessage=function(e){\n"
|
||||
" const d=JSON.parse(e.data);\n"
|
||||
" document.getElementById('uptime').textContent=fmt(d.uptime);\n"
|
||||
" document.getElementById('active').textContent=d.active;\n"
|
||||
" document.getElementById('maxconn').textContent='max '+d.max;\n"
|
||||
" document.getElementById('total').textContent=d.total;\n"
|
||||
" document.getElementById('dist').textContent=d.r2xx+' / '+d.r3xx+' / '+d.r4xx+' / '+d.r5xx;\n"
|
||||
" document.getElementById('sub').textContent='Version '+d.version+' · Updated '+new Date().toLocaleTimeString();\n"
|
||||
" const tot=Math.max(1,d.total);\n"
|
||||
" document.getElementById('b2').style.width=(d.r2xx/tot*100)+'%';\n"
|
||||
" document.getElementById('b3').style.width=(d.r3xx/tot*100)+'%';\n"
|
||||
" document.getElementById('b4').style.width=(d.r4xx/tot*100)+'%';\n"
|
||||
" document.getElementById('b5').style.width=(d.r5xx/tot*100)+'%';\n"
|
||||
"};\n"
|
||||
"evt.onerror=function(){document.getElementById('sub').textContent='Disconnected';};\n"
|
||||
"function fmt(s){const h=Math.floor(s/3600),m=Math.floor((s%3600)/60),sec=s%60;return h+'h '+m+'m '+sec+'s';}\n"
|
||||
"</script>\n"
|
||||
"</body>\n"
|
||||
"</html>";
|
||||
|
||||
/**
|
||||
* dashboard_send_html - 发送 Dashboard HTML 页面
|
||||
*/
|
||||
static int dashboard_send_html(int fd, bool keep_alive) {
|
||||
size_t html_len = strlen(DASHBOARD_HTML);
|
||||
|
||||
char header[512];
|
||||
int header_len = snprintf(header, sizeof(header),
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: text/html; charset=utf-8\r\n"
|
||||
"Content-Length: %zu\r\n"
|
||||
"Connection: %s\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n",
|
||||
html_len,
|
||||
keep_alive ? "keep-alive" : "close");
|
||||
|
||||
if (send(fd, header, (size_t)header_len, 0) < 0) return -1;
|
||||
if (send(fd, DASHBOARD_HTML, html_len, 0) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* dashboard_build_metrics_json - 构建当前指标 JSON
|
||||
*/
|
||||
static int dashboard_build_metrics_json(char *buf, size_t buflen) {
|
||||
time_t now = time(NULL);
|
||||
time_t uptime = (g_server_start_time > 0) ? (now - g_server_start_time) : 0;
|
||||
int active = atomic_load(&g_active_connections);
|
||||
unsigned int total = atomic_load(&g_total_requests);
|
||||
unsigned int r2xx = atomic_load(&g_response_2xx);
|
||||
unsigned int r3xx = atomic_load(&g_response_3xx);
|
||||
unsigned int r4xx = atomic_load(&g_response_4xx);
|
||||
unsigned int r5xx = atomic_load(&g_response_5xx);
|
||||
|
||||
return snprintf(buf, buflen,
|
||||
"{"
|
||||
"\"uptime\":%ld,"
|
||||
"\"active\":%d,"
|
||||
"\"max\":%u,"
|
||||
"\"total\":%u,"
|
||||
"\"r2xx\":%u,"
|
||||
"\"r3xx\":%u,"
|
||||
"\"r4xx\":%u,"
|
||||
"\"r5xx\":%u,"
|
||||
"\"version\":\"Cocoon/1.0\""
|
||||
"}",
|
||||
uptime, active, g_max_connections,
|
||||
total, r2xx, r3xx, r4xx, r5xx);
|
||||
}
|
||||
|
||||
/**
|
||||
* dashboard_handle_request - 处理 /_status 页面请求
|
||||
*/
|
||||
bool dashboard_handle_request(int fd, const http_request_t *req) {
|
||||
if (!req || strcmp(req->path, "/_status") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req->method != HTTP_GET) {
|
||||
const char *resp =
|
||||
"HTTP/1.1 405 Method Not Allowed\r\n"
|
||||
"Allow: GET\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n";
|
||||
send(fd, resp, strlen(resp), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
log_info("Dashboard 页面请求 fd=%d", fd);
|
||||
dashboard_send_html(fd, req->keep_alive);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* dashboard_sse_handle_request - 处理 /_status/events SSE 流
|
||||
*
|
||||
* 每 2 秒推送一次 JSON 指标,包含心跳注释防止代理超时。
|
||||
*/
|
||||
bool dashboard_sse_handle_request(int fd, const http_request_t *req) {
|
||||
if (!req || strcmp(req->path, "/_status/events") != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (req->method != HTTP_GET) {
|
||||
const char *resp =
|
||||
"HTTP/1.1 405 Method Not Allowed\r\n"
|
||||
"Allow: GET\r\n"
|
||||
"Content-Length: 0\r\n"
|
||||
"Connection: close\r\n"
|
||||
"Server: Cocoon/1.0\r\n"
|
||||
"\r\n";
|
||||
send(fd, resp, strlen(resp), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
log_info("Dashboard SSE 连接建立 fd=%d", fd);
|
||||
|
||||
if (sse_send_headers(fd) != 0) {
|
||||
log_warn("Dashboard SSE 发送响应头失败 fd=%d", fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 发送连接确认事件 */
|
||||
sse_send_event(fd, "connected", "{\"status\":\"ok\"}", 0);
|
||||
|
||||
uint32_t tick = 0;
|
||||
|
||||
while (1) {
|
||||
char json[1024];
|
||||
int json_len = dashboard_build_metrics_json(json, sizeof(json));
|
||||
if (json_len > 0) {
|
||||
if (sse_send_event(fd, "metrics", json, tick + 1) != 0) {
|
||||
log_info("Dashboard SSE 客户端断开 fd=%d", fd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
tick++;
|
||||
|
||||
/* 每 3 个事件(6 秒)发送一次心跳注释 */
|
||||
if (tick % 3 == 0) {
|
||||
if (sse_send_comment(fd, "heartbeat") != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* 2 秒间隔(协程安全) */
|
||||
coco_sleep(2000);
|
||||
}
|
||||
|
||||
log_info("Dashboard SSE 连接结束 fd=%d", fd);
|
||||
return true;
|
||||
}
|
||||
54
dashboard.h
Normal file
54
dashboard.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* dashboard.h - 内置管理 Dashboard
|
||||
*
|
||||
* 提供 `/_status` HTML 管理页面和 `/_status/events` SSE 实时指标流。
|
||||
*
|
||||
* @author xfy
|
||||
*/
|
||||
|
||||
#ifndef DASHBOARD_H
|
||||
#define DASHBOARD_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <stdatomic.h>
|
||||
#include "http.h"
|
||||
|
||||
/* 从 server.c 暴露的指标变量 */
|
||||
extern atomic_int g_active_connections;
|
||||
extern time_t g_server_start_time;
|
||||
extern uint32_t g_max_connections;
|
||||
extern atomic_uint g_total_requests;
|
||||
extern atomic_uint g_response_2xx;
|
||||
extern atomic_uint g_response_3xx;
|
||||
extern atomic_uint g_response_4xx;
|
||||
extern atomic_uint g_response_5xx;
|
||||
extern atomic_uint g_response_200;
|
||||
extern atomic_uint g_response_404;
|
||||
|
||||
/**
|
||||
* dashboard_handle_request - 处理内置 Dashboard 页面请求
|
||||
*
|
||||
* 端点路径:/_status
|
||||
* 返回自包含的 HTML 页面,通过 EventSource 连接 /_status/events 实时刷新指标。
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
* @param req HTTP 请求
|
||||
* @return true 请求已处理,false 非 Dashboard 请求
|
||||
*/
|
||||
bool dashboard_handle_request(int fd, const http_request_t *req);
|
||||
|
||||
/**
|
||||
* dashboard_sse_handle_request - 处理 Dashboard SSE 实时指标流
|
||||
*
|
||||
* 端点路径:/_status/events
|
||||
* 以 SSE 格式每 2 秒推送一次当前服务器指标 JSON。
|
||||
*
|
||||
* @param fd 客户端 socket
|
||||
* @param req HTTP 请求
|
||||
* @return true 连接保持(已转入 SSE 模式),false 非 SSE 请求或错误
|
||||
*/
|
||||
bool dashboard_sse_handle_request(int fd, const http_request_t *req);
|
||||
|
||||
#endif /* DASHBOARD_H */
|
||||
41
server.c
41
server.c
@ -34,6 +34,7 @@
|
||||
#include "config.h"
|
||||
#include "fcgi_handler.h"
|
||||
#include "cache.h"
|
||||
#include "dashboard.h"
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -85,19 +86,19 @@ struct server_context {
|
||||
/* 内存缓存 */
|
||||
cocoon_cache_t *cache; /**< 内存响应缓存(NULL 表示未启用) */
|
||||
};
|
||||
static atomic_int g_active_connections = 0;
|
||||
/* 服务器启动时间 */
|
||||
static time_t g_server_start_time = 0;
|
||||
time_t g_server_start_time = 0;
|
||||
/* 最大连接数(供健康检查端点使用) */
|
||||
static uint32_t g_max_connections = 0;
|
||||
uint32_t g_max_connections = 0;
|
||||
/* Prometheus 指标计数器 */
|
||||
static atomic_uint g_total_requests = 0;
|
||||
static atomic_uint g_response_2xx = 0;
|
||||
static atomic_uint g_response_3xx = 0;
|
||||
static atomic_uint g_response_4xx = 0;
|
||||
static atomic_uint g_response_5xx = 0;
|
||||
static atomic_uint g_response_200 = 0;
|
||||
static atomic_uint g_response_404 = 0;
|
||||
atomic_int g_active_connections = 0;
|
||||
atomic_uint g_total_requests = 0;
|
||||
atomic_uint g_response_2xx = 0;
|
||||
atomic_uint g_response_3xx = 0;
|
||||
atomic_uint g_response_4xx = 0;
|
||||
atomic_uint g_response_5xx = 0;
|
||||
atomic_uint g_response_200 = 0;
|
||||
atomic_uint g_response_404 = 0;
|
||||
|
||||
/**
|
||||
* update_metrics - 更新 Prometheus 指标计数器
|
||||
@ -473,6 +474,26 @@ static bool handle_request(connection_t *conn, const char *root_dir) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dashboard SSE 端点(必须在 /_status 之前检查) */
|
||||
if (dashboard_sse_handle_request(conn->fd, &req)) {
|
||||
conn->response_status = 200;
|
||||
update_metrics(conn->response_status);
|
||||
access_log_write((struct sockaddr *)&conn->client_addr, conn->addr_len,
|
||||
&req, conn->response_status, -1);
|
||||
http_request_free(&req);
|
||||
return true; /* SSE 保持连接 */
|
||||
}
|
||||
|
||||
/* Dashboard 页面端点 */
|
||||
if (dashboard_handle_request(conn->fd, &req)) {
|
||||
conn->response_status = 200;
|
||||
update_metrics(conn->response_status);
|
||||
access_log_write((struct sockaddr *)&conn->client_addr, conn->addr_len,
|
||||
&req, conn->response_status, -1);
|
||||
http_request_free(&req);
|
||||
return req.keep_alive;
|
||||
}
|
||||
|
||||
/* SSE 端点 */
|
||||
if (sse_handle_request(conn->fd, &req)) {
|
||||
conn->response_status = 200;
|
||||
|
||||
110
tests/unit/test_dashboard.c
Normal file
110
tests/unit/test_dashboard.c
Normal file
@ -0,0 +1,110 @@
|
||||
#include "unity.h"
|
||||
#include "dashboard.h"
|
||||
#include "http.h"
|
||||
#include <string.h>
|
||||
|
||||
/* 为测试提供全局指标变量的定义(server.c 中定义的副本) */
|
||||
atomic_int g_active_connections = 0;
|
||||
time_t g_server_start_time = 0;
|
||||
uint32_t g_max_connections = 0;
|
||||
atomic_uint g_total_requests = 0;
|
||||
atomic_uint g_response_2xx = 0;
|
||||
atomic_uint g_response_3xx = 0;
|
||||
atomic_uint g_response_4xx = 0;
|
||||
atomic_uint g_response_5xx = 0;
|
||||
atomic_uint g_response_200 = 0;
|
||||
atomic_uint g_response_404 = 0;
|
||||
|
||||
/* ===== dashboard_handle_request 路径匹配测试 ===== */
|
||||
|
||||
void test_dashboard_not_match(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_GET;
|
||||
strncpy(req.path, "/index.html", sizeof(req.path) - 1);
|
||||
/* fd = -1 不会实际发送,但匹配逻辑不受影响 */
|
||||
TEST_ASSERT_FALSE(dashboard_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_match_status(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_GET;
|
||||
strncpy(req.path, "/_status", sizeof(req.path) - 1);
|
||||
req.keep_alive = false;
|
||||
/* fd = -1 会导致 send 失败,但函数应返回 true(请求已识别) */
|
||||
TEST_ASSERT_TRUE(dashboard_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_post_not_allowed(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_POST;
|
||||
strncpy(req.path, "/_status", sizeof(req.path) - 1);
|
||||
/* 非 GET 应返回 405,但函数仍返回 true(请求已处理) */
|
||||
TEST_ASSERT_TRUE(dashboard_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_null_req(void) {
|
||||
TEST_ASSERT_FALSE(dashboard_handle_request(-1, NULL));
|
||||
}
|
||||
|
||||
/* ===== dashboard_sse_handle_request 路径匹配测试 ===== */
|
||||
|
||||
void test_dashboard_sse_not_match(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_GET;
|
||||
strncpy(req.path, "/_sse", sizeof(req.path) - 1);
|
||||
TEST_ASSERT_FALSE(dashboard_sse_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_sse_match_events(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_GET;
|
||||
strncpy(req.path, "/_status/events", sizeof(req.path) - 1);
|
||||
/* fd = -1 会导致 send 失败,快速退出循环,返回 true */
|
||||
TEST_ASSERT_TRUE(dashboard_sse_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_sse_post_not_allowed(void) {
|
||||
http_request_t req = {0};
|
||||
req.method = HTTP_POST;
|
||||
strncpy(req.path, "/_status/events", sizeof(req.path) - 1);
|
||||
TEST_ASSERT_TRUE(dashboard_sse_handle_request(-1, &req));
|
||||
}
|
||||
|
||||
void test_dashboard_sse_null_req(void) {
|
||||
TEST_ASSERT_FALSE(dashboard_sse_handle_request(-1, NULL));
|
||||
}
|
||||
|
||||
/* ===== 指标变量存在性验证 ===== */
|
||||
|
||||
void test_metrics_variables_exist(void) {
|
||||
/* 确认全局指标变量可被访问(链接期检查) */
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_active_connections));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_total_requests));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_2xx));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_3xx));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_4xx));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_5xx));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_200));
|
||||
TEST_ASSERT_EQUAL_INT(0, atomic_load(&g_response_404));
|
||||
TEST_ASSERT_EQUAL_UINT(0, g_max_connections);
|
||||
TEST_ASSERT_EQUAL_INT64(0, g_server_start_time);
|
||||
}
|
||||
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_dashboard_not_match);
|
||||
RUN_TEST(test_dashboard_match_status);
|
||||
RUN_TEST(test_dashboard_post_not_allowed);
|
||||
RUN_TEST(test_dashboard_null_req);
|
||||
RUN_TEST(test_dashboard_sse_not_match);
|
||||
RUN_TEST(test_dashboard_sse_match_events);
|
||||
RUN_TEST(test_dashboard_sse_post_not_allowed);
|
||||
RUN_TEST(test_dashboard_sse_null_req);
|
||||
RUN_TEST(test_metrics_variables_exist);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user