diff --git a/Makefile b/Makefile index 1a6fbfb..4f84806 100644 --- a/Makefile +++ b/Makefile @@ -84,6 +84,12 @@ $(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 $(UNITY_SRC) $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_static.c http.c log.c $(UNITY_SRC) -lm -lz +$(UNIT_TEST_DIR)/test_log: $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) + $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_log.c log.c $(UNITY_SRC) -lm + +$(UNIT_TEST_DIR)/test_config: $(UNIT_TEST_DIR)/test_config.c config.c log.c $(UNITY_SRC) + $(CC) $(CFLAGS) -D_GNU_SOURCE -I. -I$(UNIT_TEST_DIR)/../unity -o $@ $(UNIT_TEST_DIR)/test_config.c config.c log.c $(UNITY_SRC) -lm + # 编译规则 %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ @@ -105,4 +111,4 @@ clean: # 重新构建 rebuild: clean all -.PHONY: all clean install uninstall rebuild deps build-all test bench unit-test +.PHONY: all clean install uninstall rebuild deps build-all test bench unit-test test-all diff --git a/http.c b/http.c index bd4b7e6..226b75b 100644 --- a/http.c +++ b/http.c @@ -245,7 +245,7 @@ int http_format_response_header(char *buf, size_t buf_size, const http_response_ /* 状态行 */ if (resp->has_range) { - n += snprintf(buf + n, buf_size - n, + n += snprintf(buf + n, buf_size - (size_t)n > buf_size ? 0 : buf_size - (size_t)n, "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" @@ -256,7 +256,7 @@ int http_format_response_header(char *buf, size_t buf_size, const http_response_ (long)resp->content_length, (long)resp->range_start, (long)resp->range_end, (long)resp->total_length); } else { - n += snprintf(buf + n, buf_size - n, + n += snprintf(buf + n, buf_size - (size_t)n > buf_size ? 0 : buf_size - (size_t)n, "HTTP/1.1 %d %s\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n" @@ -268,21 +268,37 @@ int http_format_response_header(char *buf, size_t buf_size, const http_response_ /* 缓存头 */ if (resp->etag && resp->etag[0]) { - n += snprintf(buf + n, buf_size - n, "ETag: %s\r\n", resp->etag); + if ((size_t)n < buf_size) { + n += snprintf(buf + n, buf_size - (size_t)n, "ETag: %s\r\n", resp->etag); + } else { + n += (int)strlen(resp->etag) + 8; + } } if (resp->last_modified && resp->last_modified[0]) { - n += snprintf(buf + n, buf_size - n, "Last-Modified: %s\r\n", resp->last_modified); + if ((size_t)n < buf_size) { + n += snprintf(buf + n, buf_size - (size_t)n, "Last-Modified: %s\r\n", resp->last_modified); + } else { + n += (int)strlen(resp->last_modified) + 16; + } } if (resp->content_encoding && resp->content_encoding[0]) { - n += snprintf(buf + n, buf_size - n, "Content-Encoding: %s\r\n", resp->content_encoding); + if ((size_t)n < buf_size) { + n += snprintf(buf + n, buf_size - (size_t)n, "Content-Encoding: %s\r\n", resp->content_encoding); + } else { + n += (int)strlen(resp->content_encoding) + 20; + } } /* 连接头 + 空行 */ - n += snprintf(buf + n, buf_size - n, - "Connection: %s\r\n" - "Server: Cocoon/1.0\r\n" - "\r\n", - resp->keep_alive ? "keep-alive" : "close"); + if ((size_t)n < buf_size) { + n += snprintf(buf + n, buf_size - (size_t)n, + "Connection: %s\r\n" + "Server: Cocoon/1.0\r\n" + "\r\n", + resp->keep_alive ? "keep-alive" : "close"); + } else { + n += 35; /* approximate */ + } if ((size_t)n >= buf_size) return -1; return n; diff --git a/tests/unit/test_config b/tests/unit/test_config new file mode 100755 index 0000000..89e7b78 Binary files /dev/null and b/tests/unit/test_config differ diff --git a/tests/unit/test_config.c b/tests/unit/test_config.c new file mode 100644 index 0000000..b0c4ac6 --- /dev/null +++ b/tests/unit/test_config.c @@ -0,0 +1,206 @@ +#include "unity.h" +#include "config.h" +#include +#include +#include +#include + +/* 辅助:写临时配置文件,返回路径 */ +static const char *write_temp_config(const char *content) { + static char path[256]; + static int counter = 0; + snprintf(path, sizeof(path), "/tmp/cocoon_test_config_%d_%d.json", + (int)getpid(), counter++); + FILE *fp = fopen(path, "w"); + if (fp) { + fwrite(content, 1, strlen(content), fp); + fclose(fp); + } + return path; +} + +static void cleanup(const char *path) { + remove(path); +} + +/* ===== config_load_from_file ===== */ + +void test_load_valid_config(void) { + const char *p = write_temp_config( + "{\n" + " \"root_dir\": \"/var/www\",\n" + " \"port\": 8080,\n" + " \"threaded\": true,\n" + " \"num_workers\": 8,\n" + " \"max_connections\": 5000,\n" + " \"timeout_ms\": 60000,\n" + " \"log_level\": \"debug\"\n" + "}\n" + ); + cocoon_config_t cfg = {0}; + TEST_ASSERT_TRUE(config_load_from_file(p, &cfg)); + TEST_ASSERT_EQUAL_STRING("/var/www", cfg.root_dir); + TEST_ASSERT_EQUAL(8080, cfg.port); + TEST_ASSERT_TRUE(cfg.threaded); + TEST_ASSERT_EQUAL(8, cfg.num_workers); + TEST_ASSERT_EQUAL(5000, cfg.max_connections); + TEST_ASSERT_EQUAL(60000, cfg.timeout_ms); + TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, cfg.log_level); + free((void *)cfg.root_dir); + cleanup(p); +} + +void test_load_valid_minimal(void) { + const char *p = write_temp_config("{\"port\": 3000}"); + cocoon_config_t cfg = {0}; + TEST_ASSERT_TRUE(config_load_from_file(p, &cfg)); + TEST_ASSERT_EQUAL(3000, cfg.port); + free((void *)cfg.root_dir); + cleanup(p); +} + +void test_load_valid_with_comments(void) { + const char *p = write_temp_config( + "{\n" + " // this is a comment\n" + " \"port\": 9090\n" + "}\n" + ); + cocoon_config_t cfg = {0}; + TEST_ASSERT_TRUE(config_load_from_file(p, &cfg)); + TEST_ASSERT_EQUAL(9090, cfg.port); + free((void *)cfg.root_dir); + cleanup(p); +} + +void test_load_invalid_json(void) { + const char *p = write_temp_config("not json"); + cocoon_config_t cfg = {0}; + TEST_ASSERT_FALSE(config_load_from_file(p, &cfg)); + cleanup(p); +} + +void test_load_missing_file(void) { + cocoon_config_t cfg = {0}; + TEST_ASSERT_FALSE(config_load_from_file( + "/tmp/cocoon_nonexist_12345.json", &cfg)); +} + +void test_load_empty_file(void) { + const char *p = write_temp_config(""); + cocoon_config_t cfg = {0}; + TEST_ASSERT_FALSE(config_load_from_file(p, &cfg)); + cleanup(p); +} + +void test_load_null_args(void) { + TEST_ASSERT_FALSE(config_load_from_file(NULL, NULL)); + TEST_ASSERT_FALSE(config_load_from_file("/tmp/x", NULL)); +} + +/* ===== config_merge ===== */ + +void test_merge_override_all(void) { + cocoon_config_t base = { + .root_dir = strdup("/old"), + .port = 8080, + .threaded = false, + .num_workers = 2, + .max_connections = 100, + .timeout_ms = 30000, + .log_level = LOG_LEVEL_INFO + }; + cocoon_config_t cmdline = { + .root_dir = "/new", + .port = 9090, + .threaded = true, + .num_workers = 4, + .max_connections = 200, + .timeout_ms = 60000, + .log_level = LOG_LEVEL_DEBUG + }; + config_merge(&base, &cmdline, + true, true, true, true, true, true); + TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); + TEST_ASSERT_EQUAL(9090, base.port); + TEST_ASSERT_TRUE(base.threaded); + TEST_ASSERT_EQUAL(4, base.num_workers); + TEST_ASSERT_EQUAL(200, base.max_connections); + TEST_ASSERT_EQUAL(60000, base.timeout_ms); + TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, base.log_level); + free((void *)base.root_dir); +} + +void test_merge_no_override(void) { + cocoon_config_t base = { + .root_dir = strdup("/old"), + .port = 8080, + .threaded = false, + .num_workers = 2, + .log_level = LOG_LEVEL_INFO + }; + cocoon_config_t cmdline = { + .root_dir = "/new", + .port = 9090, + .num_workers = 4, + .log_level = LOG_LEVEL_DEBUG + }; + config_merge(&base, &cmdline, + false, false, false, false, false, false); + TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); + TEST_ASSERT_EQUAL(8080, base.port); + TEST_ASSERT_FALSE(base.threaded); + TEST_ASSERT_EQUAL(2, base.num_workers); + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, base.log_level); + free((void *)base.root_dir); +} + +void test_merge_partial_override(void) { + cocoon_config_t base = { + .root_dir = strdup("/old"), + .port = 8080, + .num_workers = 2, + .log_level = LOG_LEVEL_INFO + }; + cocoon_config_t cmdline = { + .root_dir = "/new", + .port = 9090, + .num_workers = 4, + .log_level = LOG_LEVEL_DEBUG + }; + config_merge(&base, &cmdline, + true, false, false, true, false, false); + TEST_ASSERT_EQUAL_STRING("/new", base.root_dir); /* overridden */ + TEST_ASSERT_EQUAL(8080, base.port); /* not overridden */ + TEST_ASSERT_EQUAL(2, base.num_workers); /* not overridden */ + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, base.log_level); /* not overridden */ + free((void *)base.root_dir); +} + +void test_merge_null_safety(void) { + /* 不应 crash */ + config_merge(NULL, NULL, true, true, true, true, true, true); + TEST_ASSERT_TRUE(1); +} + +void setUp(void) {} +void tearDown(void) {} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_load_valid_config); + RUN_TEST(test_load_valid_minimal); + RUN_TEST(test_load_valid_with_comments); + RUN_TEST(test_load_invalid_json); + RUN_TEST(test_load_missing_file); + RUN_TEST(test_load_empty_file); + RUN_TEST(test_load_null_args); + + RUN_TEST(test_merge_override_all); + RUN_TEST(test_merge_no_override); + RUN_TEST(test_merge_partial_override); + RUN_TEST(test_merge_null_safety); + + return UNITY_END(); +} diff --git a/tests/unit/test_http b/tests/unit/test_http index a95cc19..a3f1c8a 100755 Binary files a/tests/unit/test_http and b/tests/unit/test_http differ diff --git a/tests/unit/test_http.c b/tests/unit/test_http.c index 596633a..4e08690 100644 --- a/tests/unit/test_http.c +++ b/tests/unit/test_http.c @@ -179,11 +179,8 @@ void test_parse_keep_alive_override(void) { http_request_t parsed = {0}; http_parse_request(req, strlen(req), &parsed); - /* 解析器先设置 HTTP/1.1 默认 keep-alive=true,然后解析头部时,如果 Connection: close 则设为 false */ - /* 但当前实现是 Connection: keep-alive 才设为 true,否则保持 false */ - /* 实际上 HTTP/1.1 默认 keep-alive,这里应该是 true 因为代码逻辑是先设置默认值,再解析头部 */ - /* 修改测试以匹配实际行为:代码在解析头部后没有强制覆盖默认值 */ - TEST_ASSERT_TRUE(parsed.keep_alive); /* HTTP/1.1 默认保持 */ + /* HTTP/1.1 默认 keep-alive=true,但 Connection: close 应覆盖为 false */ + TEST_ASSERT_FALSE(parsed.keep_alive); http_request_free(&parsed); } diff --git a/tests/unit/test_http_asan b/tests/unit/test_http_asan index 7815e2d..4d7ce2e 100755 Binary files a/tests/unit/test_http_asan and b/tests/unit/test_http_asan differ diff --git a/tests/unit/test_log b/tests/unit/test_log new file mode 100755 index 0000000..3fff7b4 Binary files /dev/null and b/tests/unit/test_log differ diff --git a/tests/unit/test_log.c b/tests/unit/test_log.c new file mode 100644 index 0000000..23ea0ec --- /dev/null +++ b/tests/unit/test_log.c @@ -0,0 +1,81 @@ +#include "unity.h" +#include "log.h" + +/* ===== log_get_level / log_set_level ===== */ + +void test_default_level_is_info(void) { + /* 静态全局变量默认初始化为 LOG_LEVEL_INFO */ + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, log_get_level()); +} + +void test_set_level_error(void) { + log_set_level(LOG_LEVEL_ERROR); + TEST_ASSERT_EQUAL(LOG_LEVEL_ERROR, log_get_level()); +} + +void test_set_level_warn(void) { + log_set_level(LOG_LEVEL_WARN); + TEST_ASSERT_EQUAL(LOG_LEVEL_WARN, log_get_level()); +} + +void test_set_level_debug(void) { + log_set_level(LOG_LEVEL_DEBUG); + TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, log_get_level()); +} + +void test_set_level_info(void) { + log_set_level(LOG_LEVEL_INFO); + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, log_get_level()); +} + +/* ===== log_set_prefix ===== */ + +void test_set_prefix(void) { + log_set_prefix("[Test]"); + /* 无返回值,仅确认不 crash */ + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, log_get_level()); +} + +void test_set_prefix_null(void) { + log_set_prefix(NULL); + /* NULL 表示无前缀,不 crash */ + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, log_get_level()); +} + +/* ===== 边界:多次设置 ===== */ + +void test_level_persistence(void) { + log_set_level(LOG_LEVEL_DEBUG); + TEST_ASSERT_EQUAL(LOG_LEVEL_DEBUG, log_get_level()); + log_set_level(LOG_LEVEL_ERROR); + TEST_ASSERT_EQUAL(LOG_LEVEL_ERROR, log_get_level()); + log_set_level(LOG_LEVEL_INFO); + TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, log_get_level()); +} + +void setUp(void) { + /* 每个测试前重置为默认 */ + log_set_level(LOG_LEVEL_INFO); + log_set_prefix("[Cocoon]"); +} + +void tearDown(void) { + /* 每个测试后重置 */ + log_set_level(LOG_LEVEL_INFO); + log_set_prefix("[Cocoon]"); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_default_level_is_info); + RUN_TEST(test_set_level_error); + RUN_TEST(test_set_level_warn); + RUN_TEST(test_set_level_debug); + RUN_TEST(test_set_level_info); + RUN_TEST(test_set_prefix); + RUN_TEST(test_set_prefix_null); + RUN_TEST(test_level_persistence); + + return UNITY_END(); +} diff --git a/tests/unit/test_static b/tests/unit/test_static index c85c84d..f3a486b 100755 Binary files a/tests/unit/test_static and b/tests/unit/test_static differ