feat(controller-status): libxce xcecli_ctrlstatus any-node read + decode + free (task 3.1)

This commit is contained in:
Rongsong Shen 2026-06-20 05:18:04 +00:00
parent e56409cbd4
commit 691cd9199e
12 changed files with 285 additions and 1 deletions

View file

@ -0,0 +1,211 @@
/* test_ctrlstatus.c — Unity tests for the controller-status (ctrlstatus)
* libxce client reply path (controller-status task 3.1).
*
* Proves the reply path that xcecli_ctrlstatus relies on:
* 1. A true ENCODE→DECODE round-trip of xce_ctrlstatus_t through the
* generated Cap'n Proto codec (encode_CtrlStatus/write_CtrlStatus →
* bytes → read_CtrlStatus/decode_CtrlStatus): every status field,
* including the peers list and the configChecksum hex Text, survives
* the wire (Req 1.1 — the client decodes every field a handler emits).
* 2. xcecli_ctrlstatus(NULL, ...) returns a typed negative rather than a
* partial/empty success (Req 7.3 — no reachable controller ⇒ typed
* failure).
*
* A full live round-trip (client → running controller → handler reply)
* needs a controller and is the E2E task; here we prove the encode/decode
* round-trip + the no-context typed failure only.
*/
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "client.h"
#include "request.h"
#include "xce/types.h"
/* generated Cap'n Proto codec for CtrlStatus / CtrlPeer */
#include "controller.capnp.h"
/* ── encode→decode round-trip of xce_ctrlstatus_t ───────────────────────── */
static void test_ctrlstatus_roundtrip_all_fields(void)
{
/* Arrange: a fully-populated status with two configured peers */
xce_ctrlpeer_t p0 = {
.name = "ctrl-a", .host = "10.0.0.1", .port = 9991
};
xce_ctrlpeer_t p1 = {
.name = "ctrl-b", .host = "10.0.0.2", .port = 9992
};
xce_ctrlpeer_t* peerlist[2] = { &p0, &p1 };
xce_ctrlstatus_t in;
memset(&in, 0, sizeof(in));
in.clusterName = "lab-cluster";
in.primaryName = "ctrl-a";
in.responderName = "ctrl-b";
in.responderRole = 3; /* STANDBY */
in.term = 42;
in.configVersion = 0x1122334455667788ULL;
in.configChecksum = "1122334455667788aabbccddeeff0011"
"2233445566778899aabbccddeeff0011";
in.committedConfigVersion = 0x1122334455667788ULL;
in.committedLsn = 5000;
in.appliedLsn = 4999;
in.fenced = 1;
in.peers.count = 2;
in.peers.list = peerlist;
in.peerCount = 2;
/* Act: encode into a capnp segment, serialize to bytes, then decode
* a fresh struct back out — mirrors xce_encode/xce_decode. */
struct capn c;
struct capn_segment* cs;
struct CtrlStatus cap_in;
CtrlStatus_ptr sp;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&cap_in, 0, sizeof(cap_in));
encode_CtrlStatus(cs, &cap_in, &in);
sp = new_CtrlStatus(cs);
write_CtrlStatus(&cap_in, sp);
capn_setp(capn_root(&c), 0, sp.p);
size_t sz = capn_size(&c);
uint8_t* buf = (uint8_t*)malloc(sz);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL_size_t(sz, capn_write_mem(&c, buf, sz, 0));
capn_free(&c);
/* decode side */
struct capn dc;
struct CtrlStatus cap_out;
CtrlStatus_ptr dp;
xce_ctrlstatus_t out;
capn_init_mem(&dc, buf, sz, 0);
dp.p = capn_getp(capn_root(&dc), 0, 1);
read_CtrlStatus(&cap_out, dp);
memset(&out, 0, sizeof(out));
decode_CtrlStatus(&out, &cap_out);
/* Assert: every scalar/text field survived */
TEST_ASSERT_NOT_NULL(out.clusterName);
TEST_ASSERT_EQUAL_STRING("lab-cluster", out.clusterName);
TEST_ASSERT_NOT_NULL(out.primaryName);
TEST_ASSERT_EQUAL_STRING("ctrl-a", out.primaryName);
TEST_ASSERT_NOT_NULL(out.responderName);
TEST_ASSERT_EQUAL_STRING("ctrl-b", out.responderName);
TEST_ASSERT_EQUAL_UINT8(3, out.responderRole);
TEST_ASSERT_EQUAL_UINT64(42, out.term);
TEST_ASSERT_EQUAL_UINT64(0x1122334455667788ULL, out.configVersion);
TEST_ASSERT_NOT_NULL(out.configChecksum);
TEST_ASSERT_EQUAL_STRING(in.configChecksum, out.configChecksum);
TEST_ASSERT_EQUAL_UINT64(0x1122334455667788ULL, out.committedConfigVersion);
TEST_ASSERT_EQUAL_UINT64(5000, out.committedLsn);
TEST_ASSERT_EQUAL_UINT64(4999, out.appliedLsn);
TEST_ASSERT_EQUAL_UINT32(1, out.fenced);
TEST_ASSERT_EQUAL_UINT32(2, out.peerCount);
/* Assert: the peers list survived with both entries */
TEST_ASSERT_EQUAL_INT(2, out.peers.count);
TEST_ASSERT_NOT_NULL(out.peers.list);
TEST_ASSERT_NOT_NULL(out.peers.list[0]);
TEST_ASSERT_EQUAL_STRING("ctrl-a", out.peers.list[0]->name);
TEST_ASSERT_EQUAL_STRING("10.0.0.1", out.peers.list[0]->host);
TEST_ASSERT_EQUAL_UINT32(9991, out.peers.list[0]->port);
TEST_ASSERT_NOT_NULL(out.peers.list[1]);
TEST_ASSERT_EQUAL_STRING("ctrl-b", out.peers.list[1]->name);
TEST_ASSERT_EQUAL_STRING("10.0.0.2", out.peers.list[1]->host);
TEST_ASSERT_EQUAL_UINT32(9992, out.peers.list[1]->port);
free_CtrlStatus(&out);
capn_free(&dc);
free(buf);
printf("test_ctrlstatus_roundtrip_all_fields: PASS\n");
}
/* An "unknown" primary (empty primaryName) must round-trip as empty/NULL,
* never garbage — the client renders it as "unknown" (Req 1.3, 2.2). */
static void test_ctrlstatus_roundtrip_unknown_primary(void)
{
xce_ctrlstatus_t in;
memset(&in, 0, sizeof(in));
in.clusterName = "lab-cluster";
in.primaryName = ""; /* unknown */
in.responderName = "ctrl-a";
in.responderRole = 1; /* ELECTING */
in.peers.count = 0;
in.peers.list = NULL;
in.peerCount = 0;
struct capn c;
struct capn_segment* cs;
struct CtrlStatus cap_in;
CtrlStatus_ptr sp;
capn_init_malloc(&c);
cs = capn_root(&c).seg;
memset(&cap_in, 0, sizeof(cap_in));
encode_CtrlStatus(cs, &cap_in, &in);
sp = new_CtrlStatus(cs);
write_CtrlStatus(&cap_in, sp);
capn_setp(capn_root(&c), 0, sp.p);
size_t sz = capn_size(&c);
uint8_t* buf = (uint8_t*)malloc(sz);
TEST_ASSERT_NOT_NULL(buf);
capn_write_mem(&c, buf, sz, 0);
capn_free(&c);
struct capn dc;
struct CtrlStatus cap_out;
CtrlStatus_ptr dp;
xce_ctrlstatus_t out;
capn_init_mem(&dc, buf, sz, 0);
dp.p = capn_getp(capn_root(&dc), 0, 1);
read_CtrlStatus(&cap_out, dp);
memset(&out, 0, sizeof(out));
decode_CtrlStatus(&out, &cap_out);
TEST_ASSERT_EQUAL_STRING("ctrl-a", out.responderName);
TEST_ASSERT_EQUAL_UINT8(1, out.responderRole);
/* empty primaryName decodes to NULL or "" — not a stale/guessed value */
if (out.primaryName != NULL) {
TEST_ASSERT_EQUAL_STRING("", out.primaryName);
}
TEST_ASSERT_EQUAL_INT(0, out.peers.count);
TEST_ASSERT_EQUAL_UINT32(0, out.peerCount);
free_CtrlStatus(&out);
capn_free(&dc);
free(buf);
printf("test_ctrlstatus_roundtrip_unknown_primary: PASS\n");
}
/* ── no reachable controller ⇒ typed failure (Req 7.3) ──────────────────── */
static void test_xcecli_ctrlstatus_null_ctx_typed_failure(void)
{
xce_ctrlstatus_t* status = (xce_ctrlstatus_t*)0xdeadbeef;
int rc = xcecli_ctrlstatus(NULL, &status);
/* typed negative, not a partial/empty success */
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
printf("test_xcecli_ctrlstatus_null_ctx_typed_failure: PASS\n");
}
/* ── Suite entry point ──────────────────────────────────────────────────── */
void test_ctrlstatus(void)
{
RUN_TEST(test_ctrlstatus_roundtrip_all_fields);
RUN_TEST(test_ctrlstatus_roundtrip_unknown_primary);
RUN_TEST(test_xcecli_ctrlstatus_null_ctx_typed_failure);
}