cocoon/tests/unit/test_config.c
xfy911 f037ddaa99 Phase 5: 配置热重载校验
新增 config_validate() 函数,在热重载前对配置进行完整校验:
- 端口、工作线程、连接数、超时等数值范围检查
- TLS 证书/密钥、Basic Auth 用户名/密码成对校验
- 代理规则、虚拟主机、FastCGI 规则完整性校验
- 插件路径、日志级别、速率限制等边界检查

server_reload_config() 集成校验逻辑:
- 新配置加载后先校验,失败则拒绝重载并保留旧配置
- 错误信息通过日志输出,便于运维排查

新增 22 项单元测试覆盖 config_validate:
- 空配置、默认值、各类边界值、成对字段校验
- 代理规则、虚拟主机、FastCGI 规则校验
- 无错误缓冲区时的容错处理

编译:零警告
单元测试:465 个全部通过(原有 443 + 22 新增)
集成测试:115 项全部通过
2026-06-15 15:26:38 +08:00

599 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "unity.h"
#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
/* 辅助:写临时配置文件,返回路径 */
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"
" \"gzip_enabled\": false\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);
TEST_ASSERT_FALSE(cfg.gzip_enabled);
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_validate ===== */
void test_validate_null_config(void) {
char err[256];
TEST_ASSERT_FALSE(config_validate(NULL, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "NULL"));
}
void test_validate_valid_default(void) {
cocoon_config_t cfg = {0};
cfg.port = 8080;
cfg.log_level = LOG_LEVEL_INFO;
TEST_ASSERT_TRUE(config_validate(&cfg, NULL, 0));
}
void test_validate_port_zero(void) {
cocoon_config_t cfg = {.port = 0, .log_level = LOG_LEVEL_INFO};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "port"));
}
void test_validate_port_max(void) {
cocoon_config_t cfg = {.port = 65535, .log_level = LOG_LEVEL_INFO};
TEST_ASSERT_TRUE(config_validate(&cfg, NULL, 0));
}
void test_validate_workers_too_high(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_workers = 1025};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "workers"));
}
void test_validate_max_conn_too_high(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .max_connections = 100001};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "connections"));
}
void test_validate_timeout_too_short(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .timeout_ms = 50};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "timeout"));
}
void test_validate_timeout_too_long(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .timeout_ms = 3600001};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "timeout"));
}
void test_validate_log_level_invalid(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = 99};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "log_level"));
}
void test_validate_empty_root_dir(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .root_dir = ""};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "root_dir"));
}
void test_validate_tls_mismatch_cert_only(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .tls_cert = "/cert.pem"};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "TLS"));
}
void test_validate_tls_mismatch_key_only(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .tls_key = "/key.pem"};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "TLS"));
}
void test_validate_tls_both_set(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .tls_cert = "/cert.pem", .tls_key = "/key.pem"};
TEST_ASSERT_TRUE(config_validate(&cfg, NULL, 0));
}
void test_validate_auth_mismatch_user_only(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .auth_user = "admin"};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "Auth"));
}
void test_validate_proxy_empty_prefix(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_proxies = 1};
strcpy(cfg.proxies[0].prefix, "");
strcpy(cfg.proxies[0].target, "http://localhost:3000");
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "proxy"));
}
void test_validate_proxy_empty_target(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_proxies = 1};
strcpy(cfg.proxies[0].prefix, "/api");
strcpy(cfg.proxies[0].target, "");
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "proxy"));
}
void test_validate_proxy_pool_too_large(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_proxies = 1};
strcpy(cfg.proxies[0].prefix, "/api");
strcpy(cfg.proxies[0].target, "http://localhost:3000");
cfg.proxies[0].pool_size = 17;
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "pool_size"));
}
void test_validate_vhost_empty_name(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_vhosts = 1};
strcpy(cfg.vhosts[0].server_name, "");
strcpy(cfg.vhosts[0].root_dir, "/var/www");
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "vhost"));
}
void test_validate_fastcgi_empty_prefix(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_fastcgi = 1};
strcpy(cfg.fastcgi[0].prefix, "");
strcpy(cfg.fastcgi[0].host, "127.0.0.1");
cfg.fastcgi[0].port = 9000;
cfg.fastcgi[0].pool_size = 4;
cfg.fastcgi[0].timeout_ms = 30000;
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "fastcgi"));
}
void test_validate_fastcgi_invalid_port(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_fastcgi = 1};
strcpy(cfg.fastcgi[0].prefix, "/php");
strcpy(cfg.fastcgi[0].host, "127.0.0.1");
cfg.fastcgi[0].port = 0;
cfg.fastcgi[0].pool_size = 4;
cfg.fastcgi[0].timeout_ms = 30000;
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "fastcgi"));
}
void test_validate_fastcgi_pool_too_large(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .num_fastcgi = 1};
strcpy(cfg.fastcgi[0].prefix, "/php");
strcpy(cfg.fastcgi[0].host, "127.0.0.1");
cfg.fastcgi[0].port = 9000;
cfg.fastcgi[0].pool_size = 17;
cfg.fastcgi[0].timeout_ms = 30000;
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "fastcgi"));
}
void test_validate_rate_limit_too_high(void) {
cocoon_config_t cfg = {.port = 8080, .log_level = LOG_LEVEL_INFO, .rate_limit = 100001};
char err[256];
TEST_ASSERT_FALSE(config_validate(&cfg, err, sizeof(err)));
TEST_ASSERT_NOT_NULL(strstr(err, "rate_limit"));
}
void test_validate_no_err_buf(void) {
cocoon_config_t cfg = {.port = 0, .log_level = LOG_LEVEL_INFO};
TEST_ASSERT_FALSE(config_validate(&cfg, NULL, 0));
}
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,
.gzip_enabled = true,
.brotli_enabled = true
};
cocoon_config_t cmdline = {
.root_dir = strdup("/new"),
.port = 9090,
.threaded = true,
.num_workers = 4,
.max_connections = 200,
.timeout_ms = 60000,
.log_level = LOG_LEVEL_DEBUG,
.gzip_enabled = false,
.brotli_enabled = false
};
config_merge(&base, &cmdline,
true, true, true, true, true, true, true, true, true, true, true, true,
false, false, false, false, false);
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);
TEST_ASSERT_FALSE(base.gzip_enabled);
TEST_ASSERT_FALSE(base.brotli_enabled);
free((void *)base.root_dir);
free((void *)cmdline.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, false, false, false, false, false, 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, false, false, false, false, false, false,
false, false, false, 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_load_invalid_field_type(void) {
/* port 为字符串而非数字,应解析失败或忽略 */
const char *p = write_temp_config("{\"port\": \"not_a_number\"}");
cocoon_config_t cfg = {0};
/* 极简解析器可能忽略非法类型,不 crash 即可 */
config_load_from_file(p, &cfg);
/* port 保持默认值 0 */
TEST_ASSERT_EQUAL(0, cfg.port);
free((void *)cfg.root_dir);
cleanup(p);
}
void test_load_log_level_invalid(void) {
const char *p = write_temp_config("{\"log_level\": \"verbose\"}");
cocoon_config_t cfg = {0};
config_load_from_file(p, &cfg);
/* 非法 log_level 保持默认值 INFO */
TEST_ASSERT_EQUAL(LOG_LEVEL_INFO, cfg.log_level);
free((void *)cfg.root_dir);
cleanup(p);
}
void test_load_nested_ignored(void) {
/* 极简解析器遇到嵌套对象会失败,但至少不 crash */
const char *p = write_temp_config(
"{\n"
" \"port\": 7777,\n"
" \"nested\": {\"a\": 1}\n"
"}\n"
);
cocoon_config_t cfg = {0};
/* 当前极简解析器不支持嵌套对象,返回 false但不应 segfault */
config_load_from_file(p, &cfg);
TEST_ASSERT_TRUE(1); /* 走到这里说明没 crash */
free((void *)cfg.root_dir);
cleanup(p);
}
void test_load_array_ignored(void) {
/* 极简解析器遇到数组会失败,但至少不 crash */
const char *p = write_temp_config(
"{\n"
" \"port\": 7777,\n"
" \"arr\": [1, 2, 3]\n"
"}\n"
);
cocoon_config_t cfg = {0};
config_load_from_file(p, &cfg);
TEST_ASSERT_TRUE(1); /* 走到这里说明没 crash */
free((void *)cfg.root_dir);
cleanup(p);
}
void test_merge_cmdline_null_root_dir(void) {
/* cmdline root_dir 为 NULL不应覆盖 base */
cocoon_config_t base = {.root_dir = strdup("/old"), .port = 8080};
cocoon_config_t cmdline = {.root_dir = NULL, .port = 9090};
config_merge(&base, &cmdline, true, true, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false);
TEST_ASSERT_EQUAL_STRING("/old", base.root_dir); /* NULL 不覆盖 */
TEST_ASSERT_EQUAL(9090, base.port); /* port 覆盖 */
free((void *)base.root_dir);
}
void test_merge_null_safety(void) {
/* 不应 crash */
config_merge(NULL, NULL, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true, false);
TEST_ASSERT_TRUE(1);
}
void test_load_gzip_enabled(void) {
/* gzip_enabled: true */
const char *p1 = write_temp_config("{\"gzip_enabled\": true}");
cocoon_config_t cfg1 = {0};
TEST_ASSERT_TRUE(config_load_from_file(p1, &cfg1));
TEST_ASSERT_TRUE(cfg1.gzip_enabled);
free((void *)cfg1.root_dir);
cleanup(p1);
/* gzip_enabled: false */
const char *p2 = write_temp_config("{\"gzip_enabled\": false}");
cocoon_config_t cfg2 = {0};
TEST_ASSERT_TRUE(config_load_from_file(p2, &cfg2));
TEST_ASSERT_FALSE(cfg2.gzip_enabled);
free((void *)cfg2.root_dir);
cleanup(p2);
/* 默认值应为 true在 main 中设置,但这里 0 初始化后为 false */
const char *p3 = write_temp_config("{\"port\": 3000}");
cocoon_config_t cfg3 = {.gzip_enabled = true};
TEST_ASSERT_TRUE(config_load_from_file(p3, &cfg3));
TEST_ASSERT_TRUE(cfg3.gzip_enabled); /* 未指定时保持原值 */
free((void *)cfg3.root_dir);
cleanup(p3);
}
void test_load_brotli_enabled(void) {
/* brotli_enabled: true */
const char *p1 = write_temp_config("{\"brotli_enabled\": true}");
cocoon_config_t cfg1 = {0};
TEST_ASSERT_TRUE(config_load_from_file(p1, &cfg1));
TEST_ASSERT_TRUE(cfg1.brotli_enabled);
free((void *)cfg1.root_dir);
cleanup(p1);
/* brotli_enabled: false */
const char *p2 = write_temp_config("{\"brotli_enabled\": false}");
cocoon_config_t cfg2 = {0};
TEST_ASSERT_TRUE(config_load_from_file(p2, &cfg2));
TEST_ASSERT_FALSE(cfg2.brotli_enabled);
free((void *)cfg2.root_dir);
cleanup(p2);
/* 默认值应为 true */
const char *p3 = write_temp_config("{\"port\": 3000}");
cocoon_config_t cfg3 = {.brotli_enabled = true};
TEST_ASSERT_TRUE(config_load_from_file(p3, &cfg3));
TEST_ASSERT_TRUE(cfg3.brotli_enabled);
free((void *)cfg3.root_dir);
cleanup(p3);
}
void test_load_plugins_string(void) {
const char *p = write_temp_config(
"{\n"
" \"port\": 3000,\n"
" \"plugins\": \"plugins/hello.so\"\n"
"}\n"
);
cocoon_config_t cfg = {0};
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
TEST_ASSERT_EQUAL(1, cfg.num_plugins);
TEST_ASSERT_EQUAL_STRING("plugins/hello.so", cfg.plugins[0]);
free((void *)cfg.root_dir);
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
cleanup(p);
}
void test_load_plugins_array(void) {
const char *p = write_temp_config(
"{\n"
" \"port\": 3000,\n"
" \"plugins\": [\"plugins/a.so\"]\n"
"}\n"
);
cocoon_config_t cfg = {0};
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
TEST_ASSERT_EQUAL(1, cfg.num_plugins);
TEST_ASSERT_EQUAL_STRING("plugins/a.so", cfg.plugins[0]);
free((void *)cfg.root_dir);
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
cleanup(p);
}
void test_load_plugins_array_multiple(void) {
const char *p = write_temp_config(
"{\n"
" \"port\": 3000,\n"
" \"plugins\": [\"plugins/a.so\", \"plugins/b.so\"]\n"
"}\n"
);
cocoon_config_t cfg = {0};
TEST_ASSERT_TRUE(config_load_from_file(p, &cfg));
TEST_ASSERT_EQUAL(2, cfg.num_plugins);
TEST_ASSERT_EQUAL_STRING("plugins/a.so", cfg.plugins[0]);
TEST_ASSERT_EQUAL_STRING("plugins/b.so", cfg.plugins[1]);
free((void *)cfg.root_dir);
for (size_t i = 0; i < cfg.num_plugins; i++) free((void *)cfg.plugins[i]);
cleanup(p);
}
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_load_invalid_field_type);
RUN_TEST(test_load_log_level_invalid);
RUN_TEST(test_load_nested_ignored);
RUN_TEST(test_load_array_ignored);
RUN_TEST(test_merge_override_all);
RUN_TEST(test_merge_no_override);
RUN_TEST(test_merge_partial_override);
RUN_TEST(test_merge_null_safety);
RUN_TEST(test_merge_cmdline_null_root_dir);
RUN_TEST(test_load_gzip_enabled);
RUN_TEST(test_load_brotli_enabled);
RUN_TEST(test_load_plugins_string);
RUN_TEST(test_load_plugins_array);
RUN_TEST(test_load_plugins_array_multiple);
/* config_validate */
RUN_TEST(test_validate_null_config);
RUN_TEST(test_validate_valid_default);
RUN_TEST(test_validate_port_zero);
RUN_TEST(test_validate_port_max);
RUN_TEST(test_validate_workers_too_high);
RUN_TEST(test_validate_max_conn_too_high);
RUN_TEST(test_validate_timeout_too_short);
RUN_TEST(test_validate_timeout_too_long);
RUN_TEST(test_validate_log_level_invalid);
RUN_TEST(test_validate_empty_root_dir);
RUN_TEST(test_validate_tls_mismatch_cert_only);
RUN_TEST(test_validate_tls_mismatch_key_only);
RUN_TEST(test_validate_tls_both_set);
RUN_TEST(test_validate_auth_mismatch_user_only);
RUN_TEST(test_validate_proxy_empty_prefix);
RUN_TEST(test_validate_proxy_empty_target);
RUN_TEST(test_validate_proxy_pool_too_large);
RUN_TEST(test_validate_vhost_empty_name);
RUN_TEST(test_validate_fastcgi_empty_prefix);
RUN_TEST(test_validate_fastcgi_invalid_port);
RUN_TEST(test_validate_fastcgi_pool_too_large);
RUN_TEST(test_validate_rate_limit_too_high);
RUN_TEST(test_validate_no_err_buf);
return UNITY_END();
}