myxce/src/controller/testsuite/test_worker_join.c
Rongsong Shen a81f2a249c fix(controller-tests): make Unity test-file attribution and counters consistent suite-wide
Only 21 of 86 test_*.c group functions called their own nested
UNITY_BEGIN()/UNITY_END(). Each nested call reset Unity's global
NumberOfTests/TestFailures counters, so controller_test.c's final
`return UNITY_END();` -- the value meson test actually gates on --
only ever reflected whichever small window of tests ran since the
LAST nested reset, silently discarding pass/fail evidence for
everything before it (measured: 16 of 972 total tests in one run).
The same nested-BEGIN calls also caused PASS/FAIL banners to show
the wrong source file for tests in files that don't reset it
themselves (Unity.TestFile just carries over from whichever group
last called UNITY_BEGIN()).

Fix: replace every nested UNITY_BEGIN() with UnitySetTestFile(__FILE__)
(sets file attribution only, no counter reset) and drop the matching
UNITY_END(); add the same UnitySetTestFile(__FILE__) as the first
statement to the other group functions that had neither. The 2
genuinely standalone binaries (test_job_array_sched_push.c,
test_job_array_apiserver_push.c -- each has its own int main(), not
registered via controller_test.c's TEST() macro) are untouched.

Verified: single consolidated summary now reads
"972 Tests 0 Failures 0 Ignored OK", exactly matching the
ground-truth RUN_TEST call-site count across all 83 registered
group files (976 total minus 4 belonging to the 2 standalone
binaries). Every PASS/FAIL banner now names its own true source
file. Full xce-build.sh all regression pass clean (Ok: 9, Fail: 0).

No production code touched -- test-harness plumbing only.
2026-07-17 14:49:12 +00:00

535 lines
17 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.

/*
* test_worker_join.c — TDD tests for worker join refactor
*
* Task 1.1: Failing tests for apply_join_state (shared status/resource update)
* Task 1.2: Failing tests for init_worker (first-time join path)
* Task 1.3: Failing tests for rejoin_worker (reconnect update with admin-bit preservation)
*
* RED phase: apply_join_state / init_worker / rejoin_worker are stubs that
* return 0 without modifying state, so all assertions below fail until
* Tasks 2–3 provide real implementations.
*/
/* rtti.h must come first — it defines 'var' and 'typeinfo_t' used by kvmgr.h */
#include <rtti.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unity/unity.h>
#include "cluster.h"
#include "kvmgr.h"
#include "resobj.h"
#include "wobj.h"
#include "worker.h"
#include "xce/cluster_def.h"
#include "xce/types.h"
#include "xstring.h"
#include "zarray.h"
/* ── Stubs for controller functions not under test ───────────────────── */
/*
* collect_confvars is called by on_worker_join → worker_push_config.
* Stubbed here so worker.c links without pulling in the full dataex.c.
*/
void collect_confvars(cluster_t* c, collect_check_fn_t fn, xlist_t* l)
{
(void)c;
(void)fn;
(void)l;
}
/* ── Forward declarations for functions under test ──────────────────── */
/*
* apply_join_state is non-static in worker.c when XCE_TESTING is defined.
*
* RED: the current stub body does nothing and returns 0, so assertions
* that check the modified state will fail.
*/
int apply_join_state(cluster_t* c, worker_t* w, endpoint_t* ep,
xce_hostconfig_t* hconfig);
/*
* init_worker and rejoin_worker are exported from worker.c.
*
* RED: stubs return 0 without registering or modifying any entry.
*/
int init_worker(cluster_t* c, endpoint_t* ep, const char* host,
hostattrs_t* attrs, xce_hostconfig_t* hconfig);
int rejoin_worker(cluster_t* c, worker_t* w, endpoint_t* ep,
hostattrs_t* attrs, xce_hostconfig_t* hconfig);
/* ── Test fixture ────────────────────────────────────────────────────── */
/* Resource names and value types matching add_hostattrs_resvals in worker.c */
#define N_RES 9
static const char* g_res_names[N_RES] = {
"type", "model", "cpuf", "maxcpus", "maxmem",
"maxswap", "maxtmp", "ndisks", "slots"
};
static const int g_res_valtypes[N_RES] = {
XCE_VAL_STRING, XCE_VAL_STRING,
XCE_VAL_NUMERIC, XCE_VAL_NUMERIC, XCE_VAL_NUMERIC,
XCE_VAL_NUMERIC, XCE_VAL_NUMERIC, XCE_VAL_NUMERIC,
XCE_VAL_NUMERIC
};
typedef struct {
cluster_t c;
var workermgr;
var resmgr;
} test_cluster_t;
static test_cluster_t g_tc;
static void setup_test_cluster(void)
{
kvmgr_params_t str_params = {
.key_type = KVMGR_KEY_STRING,
.nslots = 64,
.delta = 16,
.datakiller = NULL,
};
memset(&g_tc, 0, sizeof(g_tc));
g_tc.workermgr = makeInstance(&xkvmgr_type, &str_params);
TEST_ASSERT_NOT_NULL(g_tc.workermgr);
g_tc.resmgr = makeInstance(&xkvmgr_type, &str_params);
TEST_ASSERT_NOT_NULL(g_tc.resmgr);
for (int i = 0; i < N_RES; i++) {
xce_resource_t rdef;
memset(&rdef, 0, sizeof(rdef));
rdef.name = (char*)g_res_names[i];
rdef.valtype = g_res_valtypes[i];
rdef.flags = 0;
rdef.idx = -1;
xce_resource_t* r = makeResource(g_tc.resmgr, &rdef);
TEST_ASSERT_NOT_NULL_MESSAGE(r, g_res_names[i]);
}
g_tc.c.workermgr = g_tc.workermgr;
g_tc.c.resmgr = g_tc.resmgr;
zarray_init(&g_tc.c.sharedinst, 64, NULL);
}
static void teardown_test_cluster(void)
{
zarray_fini(&g_tc.c.sharedinst);
if (g_tc.resmgr) dereference(g_tc.resmgr);
if (g_tc.workermgr) dereference(g_tc.workermgr);
memset(&g_tc, 0, sizeof(g_tc));
}
/* Create a worker in the test workermgr; attrs strings must be makeString'd. */
static worker_t* make_test_worker(const char* name, hostattrs_t* attrs)
{
xce_worker_t host;
memset(&host, 0, sizeof(host));
host.name = (char*)name;
host.attrs = attrs;
return makeWorker(g_tc.workermgr, &host);
}
/* Retrieve a numeric resource value from a worker's resvals by resource name */
static float get_worker_res_fval(worker_t* w, const char* name)
{
xce_resource_t* r = NULL;
lock(g_tc.c.resmgr, RTTI_LOCK_READ);
key_find(g_tc.c.resmgr, (void*)name, (void**)&r);
unlock(g_tc.c.resmgr);
if (r == NULL || r->idx < 0 || r->idx >= w->host.resvals.caps)
return -1.0f;
xce_val_t* v = (xce_val_t*)w->host.resvals.data[r->idx];
return (v != NULL) ? v->u.fvalue : -1.0f;
}
/* ── Task 1.1: Tests for apply_join_state ──────────────────────────── */
/*
* After apply_join_state, XCE_WORKER_UNAVAIL must be cleared. Req 4.1
*/
static void test_apply_join_state_clears_unavail(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("LINUX");
attrs.hostmodel = makeString("x86");
attrs.cpufactor = 1.0f;
attrs.maxcpus = 4;
attrs.maxmem = 16;
attrs.maxswap = 32;
attrs.maxtmp = 64;
attrs.ndisks = 1;
worker_t* w = make_test_worker("host-ajs-1", &attrs);
TEST_ASSERT_NOT_NULL(w);
lock(w, RTTI_LOCK_WRITE);
w->host.status |= XCE_WORKER_UNAVAIL;
unlock(w);
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!"; /* no type override */
hconfig.hostmodel = (char*)"!"; /* no model override */
hconfig.mxj = 2;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
lock(w, RTTI_LOCK_WRITE);
int rc = apply_join_state(&g_tc.c, w, &ep, &hconfig);
unlock(w);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_FALSE_MESSAGE(w->host.status & XCE_WORKER_UNAVAIL,
"UNAVAIL must be cleared after apply_join_state");
teardown_test_cluster();
printf("test_apply_join_state_clears_unavail: PASS\n");
}
/*
* When hconfig provides non-"!" overrides, hosttype and hostmodel must
* match those values after apply_join_state. Req 4.2
*/
static void test_apply_join_state_applies_hosttype_override(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("OLD_TYPE");
attrs.hostmodel = makeString("OLD_MODEL");
attrs.cpufactor = 1.0f;
attrs.maxcpus = 2;
attrs.maxmem = 8;
attrs.maxswap = 16;
attrs.maxtmp = 32;
attrs.ndisks = 1;
worker_t* w = make_test_worker("host-ajs-2", &attrs);
TEST_ASSERT_NOT_NULL(w);
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"LINUX64";
hconfig.hostmodel = (char*)"x86_64";
hconfig.mxj = 4;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
lock(w, RTTI_LOCK_WRITE);
int rc = apply_join_state(&g_tc.c, w, &ep, &hconfig);
unlock(w);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_STRING_MESSAGE("LINUX64", w->host.attrs->hosttype,
"hosttype must be overridden from hconfig");
TEST_ASSERT_EQUAL_STRING_MESSAGE("x86_64", w->host.attrs->hostmodel,
"hostmodel must be overridden from hconfig");
teardown_test_cluster();
printf("test_apply_join_state_applies_hosttype_override: PASS\n");
}
/*
* After apply_join_state, 'slots' equals hconfig.mxj and numeric host-attr
* resources match the worker's attrs. Req 4.2, 4.3
*/
static void test_apply_join_state_updates_resource_values(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("LINUX");
attrs.hostmodel = makeString("x86");
attrs.cpufactor = 3.0f;
attrs.maxcpus = 8;
attrs.maxmem = 64;
attrs.maxswap = 128;
attrs.maxtmp = 256;
attrs.ndisks = 4;
worker_t* w = make_test_worker("host-ajs-3", &attrs);
TEST_ASSERT_NOT_NULL(w);
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!";
hconfig.hostmodel = (char*)"!";
hconfig.mxj = 6;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
lock(w, RTTI_LOCK_WRITE);
int rc = apply_join_state(&g_tc.c, w, &ep, &hconfig);
unlock(w);
TEST_ASSERT_EQUAL_INT(0, rc);
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(6.0f, get_worker_res_fval(w, "slots"),
"slots must equal hconfig.mxj");
TEST_ASSERT_EQUAL_FLOAT(3.0f, get_worker_res_fval(w, "cpuf"));
TEST_ASSERT_EQUAL_FLOAT(8.0f, get_worker_res_fval(w, "maxcpus"));
TEST_ASSERT_EQUAL_FLOAT(64.0f, get_worker_res_fval(w, "maxmem"));
TEST_ASSERT_EQUAL_FLOAT(128.0f, get_worker_res_fval(w, "maxswap"));
TEST_ASSERT_EQUAL_FLOAT(256.0f, get_worker_res_fval(w, "maxtmp"));
TEST_ASSERT_EQUAL_FLOAT(4.0f, get_worker_res_fval(w, "ndisks"));
teardown_test_cluster();
printf("test_apply_join_state_updates_resource_values: PASS\n");
}
/* ── Task 1.2: Tests for init_worker ────────────────────────────────── */
/*
* After init_worker for a new hostname, key_find must find the entry and
* return a non-negative index. Req 2.1, 2.3
*/
static void test_init_worker_creates_new_entry(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("LINUX");
attrs.hostmodel = makeString("x86");
attrs.cpufactor = 1.0f;
attrs.maxcpus = 4;
attrs.maxmem = 16;
attrs.maxswap = 32;
attrs.maxtmp = 64;
attrs.ndisks = 1;
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!";
hconfig.hostmodel = (char*)"!";
hconfig.mxj = 4;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
int idx = init_worker(&g_tc.c, &ep, "new-host-1", &attrs, &hconfig);
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(0, idx,
"init_worker must return non-negative index");
worker_t* w = NULL;
lock(g_tc.c.workermgr, RTTI_LOCK_READ);
int rc = key_find(g_tc.c.workermgr, (void*)"new-host-1", (void**)&w);
unlock(g_tc.c.workermgr);
TEST_ASSERT_GREATER_THAN_MESSAGE(-1, rc,
"worker must be registered in workermgr after init");
TEST_ASSERT_NOT_NULL_MESSAGE(w, "key_find must return a valid worker pointer");
teardown_test_cluster();
printf("test_init_worker_creates_new_entry: PASS\n");
}
/*
* The index returned by init_worker must match w->idx. Req 2.3
*/
static void test_init_worker_returns_correct_idx(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("LINUX");
attrs.hostmodel = makeString("x86");
attrs.cpufactor = 1.0f;
attrs.maxcpus = 2;
attrs.maxmem = 8;
attrs.maxswap = 16;
attrs.maxtmp = 32;
attrs.ndisks = 1;
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!";
hconfig.hostmodel = (char*)"!";
hconfig.mxj = 2;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
int idx = init_worker(&g_tc.c, &ep, "new-host-2", &attrs, &hconfig);
TEST_ASSERT_GREATER_OR_EQUAL(0, idx);
worker_t* w = NULL;
lock(g_tc.c.workermgr, RTTI_LOCK_READ);
key_find(g_tc.c.workermgr, (void*)"new-host-2", (void**)&w);
unlock(g_tc.c.workermgr);
TEST_ASSERT_NOT_NULL(w);
TEST_ASSERT_EQUAL_INT_MESSAGE(idx, w->idx,
"returned idx must match the worker's idx field");
teardown_test_cluster();
printf("test_init_worker_returns_correct_idx: PASS\n");
}
/* ── Task 1.3: Tests for rejoin_worker ──────────────────────────────── */
/*
* rejoin_worker must update hostattrs and ep on the existing entry. Req 3.1, 3.2
*/
static void test_rejoin_worker_updates_attrs_and_ep(void)
{
setup_test_cluster();
hostattrs_t old_attrs;
memset(&old_attrs, 0, sizeof(old_attrs));
old_attrs.hosttype = makeString("OLD_TYPE");
old_attrs.hostmodel = makeString("OLD_MODEL");
old_attrs.cpufactor = 1.0f;
old_attrs.maxcpus = 2;
old_attrs.maxmem = 8;
old_attrs.maxswap = 16;
old_attrs.maxtmp = 32;
old_attrs.ndisks = 1;
worker_t* w = make_test_worker("rejoin-host-1", &old_attrs);
TEST_ASSERT_NOT_NULL(w);
hostattrs_t new_attrs;
memset(&new_attrs, 0, sizeof(new_attrs));
new_attrs.hosttype = makeString("NEW_TYPE");
new_attrs.hostmodel = makeString("NEW_MODEL");
new_attrs.cpufactor = 2.0f;
new_attrs.maxcpus = 8;
new_attrs.maxmem = 64;
new_attrs.maxswap = 128;
new_attrs.maxtmp = 256;
new_attrs.ndisks = 2;
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!";
hconfig.hostmodel = (char*)"!";
hconfig.mxj = 4;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
int idx = rejoin_worker(&g_tc.c, w, &ep, &new_attrs, &hconfig);
TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(0, idx,
"rejoin_worker must return non-negative index");
TEST_ASSERT_EQUAL_STRING_MESSAGE("NEW_TYPE", w->host.attrs->hosttype,
"hosttype must be updated to new attrs value");
TEST_ASSERT_EQUAL_STRING_MESSAGE("NEW_MODEL", w->host.attrs->hostmodel,
"hostmodel must be updated to new attrs value");
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(2.0f, w->host.attrs->cpufactor,
"cpufactor must be updated to new value");
TEST_ASSERT_EQUAL_PTR_MESSAGE(&ep, w->ep,
"ep must be updated to the new endpoint");
/* Release the test's own references to the new strings */
dereference(new_attrs.hosttype);
dereference(new_attrs.hostmodel);
teardown_test_cluster();
printf("test_rejoin_worker_updates_attrs_and_ep: PASS\n");
}
/*
* Admin-set status bits must survive a rejoin_worker call. Req 3.3
*/
static void test_rejoin_worker_preserves_admin_bits(void)
{
setup_test_cluster();
hostattrs_t attrs;
memset(&attrs, 0, sizeof(attrs));
attrs.hosttype = makeString("LINUX");
attrs.hostmodel = makeString("x86");
attrs.cpufactor = 1.0f;
attrs.maxcpus = 4;
attrs.maxmem = 16;
attrs.maxswap = 32;
attrs.maxtmp = 64;
attrs.ndisks = 1;
worker_t* w = make_test_worker("rejoin-host-2", &attrs);
TEST_ASSERT_NOT_NULL(w);
/* Set all admin bits before the rejoin */
lock(w, RTTI_LOCK_WRITE);
w->host.status |= (XCE_WORKER_LOCKEDU | XCE_WORKER_CLOSED
| XCE_WORKER_CLOSED_BY_ADMIN);
unlock(w);
hostattrs_t new_attrs;
memset(&new_attrs, 0, sizeof(new_attrs));
new_attrs.hosttype = makeString("LINUX");
new_attrs.hostmodel = makeString("x86");
new_attrs.cpufactor = 2.0f;
new_attrs.maxcpus = 8;
new_attrs.maxmem = 32;
new_attrs.maxswap = 64;
new_attrs.maxtmp = 128;
new_attrs.ndisks = 2;
xce_hostconfig_t hconfig;
memset(&hconfig, 0, sizeof(hconfig));
hconfig.hosttype = (char*)"!";
hconfig.hostmodel = (char*)"!";
hconfig.mxj = 4;
endpoint_t ep;
memset(&ep, 0, sizeof(ep));
rejoin_worker(&g_tc.c, w, &ep, &new_attrs, &hconfig);
TEST_ASSERT_TRUE_MESSAGE(w->host.status & XCE_WORKER_LOCKEDU,
"LOCKEDU must be preserved after rejoin");
TEST_ASSERT_TRUE_MESSAGE(w->host.status & XCE_WORKER_CLOSED,
"CLOSED must be preserved after rejoin");
TEST_ASSERT_TRUE_MESSAGE(w->host.status & XCE_WORKER_CLOSED_BY_ADMIN,
"CLOSED_BY_ADMIN must be preserved after rejoin");
dereference(new_attrs.hosttype);
dereference(new_attrs.hostmodel);
teardown_test_cluster();
printf("test_rejoin_worker_preserves_admin_bits: PASS\n");
}
/* ── Dispatch ───────────────────────────────────────────────────────── */
void test_worker_join(void)
{
UnitySetTestFile(__FILE__);
printf("\n>> worker join refactor tests\n\n");
/* xstring_table_init must be called once before any makeString usage */
xstring_table_init(139);
/* Task 1.1: apply_join_state (shared status/resource update) */
RUN_TEST(test_apply_join_state_clears_unavail);
RUN_TEST(test_apply_join_state_applies_hosttype_override);
RUN_TEST(test_apply_join_state_updates_resource_values);
/* Task 1.2: init_worker (first-time join) */
RUN_TEST(test_init_worker_creates_new_entry);
RUN_TEST(test_init_worker_returns_correct_idx);
/* Task 1.3: rejoin_worker (reconnect with admin-bit preservation) */
RUN_TEST(test_rejoin_worker_updates_attrs_and_ep);
RUN_TEST(test_rejoin_worker_preserves_admin_bits);
}