/* test_cluster_parse.c — unit tests for the parse_peers() helper used by * worker and scheduler main.c (P2.5-5 / P2.5-6). * * The parser is a static function in the respective main.c files. To keep * the test self-contained we duplicate a simplified reference implementation * here and test the same contract. Integration with the real binaries is * covered by the build (both must compile). */ #include #include #include #include #include "xce/cluster.h" /* ── Reference implementation of parse_peers (mirrors worker/main.c) ─── */ static int parse_peers_local(const char* peers_str, xce_cluster_config_t* cfg) { cfg->candidates_count = 0; if (!peers_str || peers_str[0] == '\0') return -1; char buf[2048]; strncpy(buf, peers_str, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; char* saveptr; char* tok = strtok_r(buf, ",", &saveptr); while (tok && cfg->candidates_count < MAX_HA_CANDIDATES) { /* Format: name=host:port OR host:port */ char* eq = strchr(tok, '='); char* addr = eq ? eq + 1 : tok; char* colon = strrchr(addr, ':'); if (!colon) return -1; *colon = '\0'; strncpy(cfg->candidates[cfg->candidates_count].hostname, addr, XCE_MAXHOSTNAME_LEN - 1); cfg->candidates[cfg->candidates_count].hostname[XCE_MAXHOSTNAME_LEN - 1] = '\0'; cfg->candidates[cfg->candidates_count].port = atoi(colon + 1); cfg->candidates_count++; tok = strtok_r(NULL, ",", &saveptr); } return cfg->candidates_count > 0 ? 0 : -1; } /* ── Test cases ─────────────────────────────────────────────────────── */ static void test_parse_peers_single(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local("ctrl-1=10.0.1.1:7000", &cfg); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(1, cfg.candidates_count); TEST_ASSERT_EQUAL_STRING("10.0.1.1", cfg.candidates[0].hostname); TEST_ASSERT_EQUAL_INT(7000, cfg.candidates[0].port); printf("test_parse_peers_single: PASS\n"); } static void test_parse_peers_multiple(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local( "ctrl-1=10.0.1.1:7000,ctrl-2=10.0.1.2:7001,ctrl-3=10.0.1.3:7002", &cfg); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(3, cfg.candidates_count); TEST_ASSERT_EQUAL_STRING("10.0.1.2", cfg.candidates[1].hostname); TEST_ASSERT_EQUAL_INT(7001, cfg.candidates[1].port); TEST_ASSERT_EQUAL_STRING("10.0.1.3", cfg.candidates[2].hostname); TEST_ASSERT_EQUAL_INT(7002, cfg.candidates[2].port); printf("test_parse_peers_multiple: PASS\n"); } static void test_parse_peers_no_name(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local("10.0.1.1:7000,10.0.1.2:7001", &cfg); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(2, cfg.candidates_count); TEST_ASSERT_EQUAL_STRING("10.0.1.1", cfg.candidates[0].hostname); TEST_ASSERT_EQUAL_INT(7000, cfg.candidates[0].port); TEST_ASSERT_EQUAL_STRING("10.0.1.2", cfg.candidates[1].hostname); TEST_ASSERT_EQUAL_INT(7001, cfg.candidates[1].port); printf("test_parse_peers_no_name: PASS\n"); } static void test_parse_peers_empty_returns_error(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local("", &cfg); TEST_ASSERT_NOT_EQUAL(0, rc); printf("test_parse_peers_empty_returns_error: PASS\n"); } static void test_parse_peers_null_returns_error(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local(NULL, &cfg); TEST_ASSERT_NOT_EQUAL(0, rc); printf("test_parse_peers_null_returns_error: PASS\n"); } static void test_parse_peers_missing_port_returns_error(void) { xce_cluster_config_t cfg = {0}; int rc = parse_peers_local("ctrl-1=10.0.1.1", &cfg); /* no colon:port */ TEST_ASSERT_NOT_EQUAL(0, rc); printf("test_parse_peers_missing_port_returns_error: PASS\n"); } static void test_parse_peers_first_entry_parsed_correctly(void) { /* Verify the name= prefix is stripped and only addr:port is stored */ xce_cluster_config_t cfg = {0}; int rc = parse_peers_local("primary=192.168.10.1:9000", &cfg); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_STRING("192.168.10.1", cfg.candidates[0].hostname); TEST_ASSERT_EQUAL_INT(9000, cfg.candidates[0].port); printf("test_parse_peers_first_entry_parsed_correctly: PASS\n"); } static void test_parse_peers_respects_max_candidates(void) { /* Build a string with MAX_HA_CANDIDATES + 2 entries; only MAX accepted */ char big[2048] = ""; char entry[128] = ""; int n = MAX_HA_CANDIDATES + 2; for (int i = 0; i < n; i++) { snprintf(entry, sizeof(entry), "10.0.1.%d:%d", i + 1, 7000 + i); if (i > 0) strncat(big, ",", sizeof(big) - strlen(big) - 1); strncat(big, entry, sizeof(big) - strlen(big) - 1); } xce_cluster_config_t cfg = {0}; int rc = parse_peers_local(big, &cfg); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(MAX_HA_CANDIDATES, cfg.candidates_count); printf("test_parse_peers_respects_max_candidates: PASS\n"); } /* ── Test group entry point ─────────────────────────────────────────── */ void test_cluster_parse(void) { RUN_TEST(test_parse_peers_single); RUN_TEST(test_parse_peers_multiple); RUN_TEST(test_parse_peers_no_name); RUN_TEST(test_parse_peers_empty_returns_error); RUN_TEST(test_parse_peers_null_returns_error); RUN_TEST(test_parse_peers_missing_port_returns_error); RUN_TEST(test_parse_peers_first_entry_parsed_correctly); RUN_TEST(test_parse_peers_respects_max_candidates); }