feat(controller-peers-env-fallback): add the shared "which candidate is me" lookup (task 1.2)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-03 05:18:10 +00:00
parent 7328b06c53
commit d4858e1fe9
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 131 additions and 1 deletions

View file

@ -148,6 +148,21 @@ int xce_parse_peers(const char* peers_str, xce_cluster_config_t* cfg);
*/
const char* xce_controller_peers_env(void);
/**
* xce_peers_find_identity() — the one "which candidate is me" rule.
*
* A candidate's key is its name when non-empty, otherwise its hostname
* (a named candidate is never matched by its hostname). The first
* candidate whose key matches name (compared over XCE_MAXNAME_LEN - 1
* characters) wins. An empty name is compared like any other, so it
* matches an unnamed candidate with an empty hostname.
*
* Returns the matching candidate's index, or -1 when cfg or name is NULL
* or nothing matches. No default is applied; each caller keeps its own
* no-match handling.
*/
int xce_peers_find_identity(const xce_cluster_config_t* cfg, const char* name);
/* Test seam: set to non-NULL to override the real connect (unit tests only) */
typedef void* (*xce_connect_fn_t)(const char* host, int port);
void xce_cluster_set_connect_fn(xce_connect_fn_t fn);

View file

@ -65,6 +65,20 @@ const char* xce_controller_peers_env(void)
return (v && v[0] != '\0') ? v : NULL;
}
int xce_peers_find_identity(const xce_cluster_config_t* cfg, const char* name)
{
if (!cfg || !name)
return -1;
for (int i = 0; i < cfg->candidates_count; i++) {
const char* key = cfg->candidates[i].name[0]
? cfg->candidates[i].name
: cfg->candidates[i].hostname;
if (strncmp(key, name, XCE_MAXNAME_LEN - 1) == 0)
return i;
}
return -1;
}
/* ── Test seam ─────────────────────────────────────────────────── */
static xce_connect_fn_t s_connect_fn = NULL;

View file

@ -326,6 +326,100 @@ static void test_peers_env_nonempty_returned_unchanged(void)
printf("test_peers_env_nonempty_returned_unchanged: PASS\n");
}
/* ── xce_peers_find_identity() ──────────────────────────────────── */
/* Fill candidate i of cfg directly: name may be "" (unnamed entry),
* hostname may be "" (only reachable by filling the struct by hand). */
static void set_candidate(xce_cluster_config_t* cfg, int i, const char* name,
const char* hostname, int port)
{
snprintf(cfg->candidates[i].name, XCE_MAXNAME_LEN, "%s", name);
snprintf(cfg->candidates[i].hostname, XCE_MAXHOSTNAME_LEN, "%s",
hostname);
cfg->candidates[i].port = port;
}
/* Req 1.6: a named candidate is found by its name. */
static void test_find_identity_named_match(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c1", "localhost", 9991);
set_candidate(&cfg, 1, "c2", "localhost", 9992);
set_candidate(&cfg, 2, "c3", "localhost", 9993);
cfg.candidates_count = 3;
TEST_ASSERT_EQUAL_INT(1, xce_peers_find_identity(&cfg, "c2"));
printf("test_find_identity_named_match: PASS\n");
}
/* Req 1.6: an unnamed candidate is found by its hostname. */
static void test_find_identity_unnamed_matched_by_hostname(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "", "node1", 9990);
set_candidate(&cfg, 1, "", "node2", 9990);
cfg.candidates_count = 2;
TEST_ASSERT_EQUAL_INT(1, xce_peers_find_identity(&cfg, "node2"));
printf("test_find_identity_unnamed_matched_by_hostname: PASS\n");
}
/* Req 1.6: a named candidate's key is its name only -- its hostname
* does not identify it. */
static void test_find_identity_named_not_matched_by_hostname(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c1", "node1", 9991);
set_candidate(&cfg, 1, "c2", "node2", 9992);
cfg.candidates_count = 2;
TEST_ASSERT_EQUAL_INT(-1, xce_peers_find_identity(&cfg, "node2"));
printf("test_find_identity_named_not_matched_by_hostname: PASS\n");
}
/* Req 1.6: no candidate matches -> -1 (no default applied). */
static void test_find_identity_no_match(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c1", "localhost", 9991);
set_candidate(&cfg, 1, "", "node2", 9992);
cfg.candidates_count = 2;
TEST_ASSERT_EQUAL_INT(-1, xce_peers_find_identity(&cfg, "c9"));
printf("test_find_identity_no_match: PASS\n");
}
/* Req 1.6: the first of duplicate names wins (not the last). */
static void test_find_identity_first_duplicate_wins(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c0", "localhost", 9990);
set_candidate(&cfg, 1, "dup", "localhost", 9991);
set_candidate(&cfg, 2, "dup", "localhost", 9992);
cfg.candidates_count = 3;
TEST_ASSERT_EQUAL_INT(1, xce_peers_find_identity(&cfg, "dup"));
printf("test_find_identity_first_duplicate_wins: PASS\n");
}
/* Req 1.6: no name given -> -1, even against an entry with empty keys. */
static void test_find_identity_null_name(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c1", "localhost", 9991);
set_candidate(&cfg, 1, "", "", 9992);
cfg.candidates_count = 2;
TEST_ASSERT_EQUAL_INT(-1, xce_peers_find_identity(&cfg, NULL));
printf("test_find_identity_null_name: PASS\n");
}
/* Req 1.6: an empty name is compared like any other, so it matches an
* unnamed empty-hostname entry exactly as the controller's loops do. */
static void test_find_identity_empty_name_matches_empty_hostname(void)
{
xce_cluster_config_t cfg = {0};
set_candidate(&cfg, 0, "c1", "localhost", 9991);
set_candidate(&cfg, 1, "", "", 9992);
cfg.candidates_count = 2;
TEST_ASSERT_EQUAL_INT(1, xce_peers_find_identity(&cfg, ""));
printf("test_find_identity_empty_name_matches_empty_hostname: PASS\n");
}
/* ── Test group entry point ─────────────────────────────────────── */
void test_cluster(void)
{
@ -349,4 +443,11 @@ void test_cluster(void)
RUN_TEST(test_peers_env_unset_returns_null);
RUN_TEST(test_peers_env_empty_returns_null);
RUN_TEST(test_peers_env_nonempty_returned_unchanged);
RUN_TEST(test_find_identity_named_match);
RUN_TEST(test_find_identity_unnamed_matched_by_hostname);
RUN_TEST(test_find_identity_named_not_matched_by_hostname);
RUN_TEST(test_find_identity_no_match);
RUN_TEST(test_find_identity_first_duplicate_wins);
RUN_TEST(test_find_identity_null_name);
RUN_TEST(test_find_identity_empty_name_matches_empty_hostname);
}