/* * test_alloc_plan_iter.c - contract test for the lazy allocation-plan and * host-candidate iterator interfaces (scheduler-allocation spec, task * 1.1, Req 2 AC 1 / Req 3 AC 1). * * This task only adds a header (sched/alloc_iter.h) declaring * xce_alloc_plan_t/xce_alloc_plan_iter_t/xce_host_iter_t -- no allocator * is wired onto them yet (parjob_allocator/seqjob_allocator rewrap is * later tasks' work). So the RED/GREEN evidence for this task is: * * RED: before sched/alloc_iter.h existed, this file failed to compile * (unknown types xce_alloc_plan_t/xce_alloc_plan_iter_t/ * xce_host_iter_t). * GREEN: with the header in place, a trivial, no-op reference * implementation of each interface (below) can be constructed, * stack-allocated and initialized in place per the documented * convention, and driven through a full init()/next()/fini() * cycle -- including the exact plan -> per-bucket host_iter * composition shape requirements.md's Composition Algorithm * describes -- proving the contract shape actually compiles and * is usable, not just that a struct literal type-checks. * * Both stub implementations ignore the real xce_primary_resreq_t/ * xce_sched_core_t/plan/workers arguments (ok, per the header's own * "each concrete plan-iterator implementation carries whatever it needs" * note) and always yield exactly one candidate before signaling stable * exhaustion. */ #include #include #include #include #include #include "sched/alloc_iter.h" /* ---- trivial no-op xce_alloc_plan_iter_t reference implementation ---- */ typedef struct { int served; /* 0 = not yet served, 1 = exhausted */ int bucket_slots[1]; xce_alloc_plan_t plan; } stub_plan_priv_t; static int stub_plan_iter_init(xce_alloc_plan_iter_t* iter, xce_primary_resreq_t* resreq, xce_sched_core_t* core) { stub_plan_priv_t* priv; /* Stub: ignores the real resreq/core state -- a real implementation * (parjob's combinatorial one, seqjob's trivial one) would derive its * plan space from resreq->maxTasks here. */ (void)resreq; (void)core; if (iter == NULL) { return -EINVAL; } priv = (stub_plan_priv_t*)calloc(1, sizeof(*priv)); if (priv == NULL) { return -ENOMEM; } priv->served = 0; priv->bucket_slots[0] = 1; priv->plan.nbuckets = 1; priv->plan.bucket_slots = priv->bucket_slots; iter->private = priv; return 0; } static xce_alloc_plan_t* stub_plan_iter_next(xce_alloc_plan_iter_t* iter) { stub_plan_priv_t* priv; /* Req 2 AC 6: a real cancellation point at least once per invocation * -- combinatorial generation has none of its own. */ pthread_testcancel(); if (iter == NULL) { return NULL; } priv = (stub_plan_priv_t*)iter->private; if (priv == NULL || priv->served) { return NULL; /* Req 2 AC 4: stable exhaustion signal */ } priv->served = 1; return &priv->plan; } static void stub_plan_iter_fini(xce_alloc_plan_iter_t* iter) { if (iter == NULL) { return; } free(iter->private); iter->private = NULL; } /* ---- trivial no-op xce_host_iter_t reference implementation ---- */ typedef struct { int served; /* 0 = not yet served, 1 = exhausted */ xce_sched_worker_t worker; } stub_host_priv_t; static int stub_host_iter_init(xce_host_iter_t* iter, xce_alloc_plan_t* plan, var workers) { stub_host_priv_t* priv; /* Stub: ignores the real plan/workers state -- the one shared real * implementation (host_iter.c, Req 3) would filter `workers` by * plan's select expression + slot availability here. */ (void)plan; (void)workers; if (iter == NULL) { return -EINVAL; } priv = (stub_host_priv_t*)calloc(1, sizeof(*priv)); if (priv == NULL) { return -ENOMEM; } priv->served = 0; priv->worker.name = "stub-host"; iter->private = priv; return 0; } static xce_sched_worker_t* stub_host_iter_next(xce_host_iter_t* iter) { stub_host_priv_t* priv; /* Req 3 AC 6: a real cancellation point at least once per invocation * -- scanning/filtering has none of its own. */ pthread_testcancel(); if (iter == NULL) { return NULL; } priv = (stub_host_priv_t*)iter->private; if (priv == NULL || priv->served) { return NULL; /* Req 3 AC 3: exhausted for this instance */ } priv->served = 1; return &priv->worker; } static void stub_host_iter_fini(xce_host_iter_t* iter) { if (iter == NULL) { return; } free(iter->private); iter->private = NULL; } void setUp(void) { return; } void tearDown(void) { return; } /* * Req 2 AC 1/2/4/5: the plan iterator is stack-allocated by the caller * and initialized in place (not a heap-allocating factory -- see the * header's own convention note), yields exactly one plan, then a stable * NULL on every subsequent next() call, and fini() releases it cleanly. */ static void test_plan_iter_stub_produces_one_plan_then_stable_null(void) { xce_alloc_plan_iter_t iter_storage; xce_alloc_plan_iter_t* iter = &iter_storage; xce_alloc_plan_t* plan; memset(iter, 0, sizeof(*iter)); iter->init = stub_plan_iter_init; iter->next = stub_plan_iter_next; iter->fini = stub_plan_iter_fini; TEST_ASSERT_EQUAL_INT(0, iter->init(iter, NULL, NULL)); plan = iter->next(iter); TEST_ASSERT_NOT_NULL(plan); TEST_ASSERT_EQUAL_INT(1, plan->nbuckets); TEST_ASSERT_NOT_NULL(plan->bucket_slots); TEST_ASSERT_EQUAL_INT(1, plan->bucket_slots[0]); /* Req 2 AC 4: end-of-iteration signal, stable across repeated calls. */ TEST_ASSERT_NULL(iter->next(iter)); TEST_ASSERT_NULL(iter->next(iter)); iter->fini(iter); TEST_ASSERT_NULL(iter->private); } /* * Req 3 AC 1/2/3/5: the host iterator is likewise stack-allocated and * initialized in place from a plan, yields exactly one candidate host, * then a stable NULL, and fini() is independent of the owning plan * iterator's own lifecycle -- proved by finiing the host iterator first * and then confirming plan_iter's own exhaustion/fini() sequence is * completely unaffected. */ static void test_host_iter_stub_produces_one_worker_then_stable_null(void) { xce_alloc_plan_iter_t plan_iter_storage; xce_alloc_plan_iter_t* plan_iter = &plan_iter_storage; xce_alloc_plan_t* plan; xce_host_iter_t host_iter_storage; xce_host_iter_t* host_iter = &host_iter_storage; xce_sched_worker_t* w; memset(plan_iter, 0, sizeof(*plan_iter)); plan_iter->init = stub_plan_iter_init; plan_iter->next = stub_plan_iter_next; plan_iter->fini = stub_plan_iter_fini; TEST_ASSERT_EQUAL_INT(0, plan_iter->init(plan_iter, NULL, NULL)); plan = plan_iter->next(plan_iter); TEST_ASSERT_NOT_NULL(plan); memset(host_iter, 0, sizeof(*host_iter)); host_iter->init = stub_host_iter_init; host_iter->next = stub_host_iter_next; host_iter->fini = stub_host_iter_fini; /* `plan` is still valid here -- neither next() nor fini() has been * called again on plan_iter since it was returned above, per the * header's own "valid until the next next()/fini() call" contract. */ TEST_ASSERT_EQUAL_INT(0, host_iter->init(host_iter, plan, NULL)); w = host_iter->next(host_iter); TEST_ASSERT_NOT_NULL(w); TEST_ASSERT_EQUAL_STRING("stub-host", w->name); /* Req 3 AC 3: exhausted for this instance's bucket search, stable. */ TEST_ASSERT_NULL(host_iter->next(host_iter)); TEST_ASSERT_NULL(host_iter->next(host_iter)); /* Req 3 AC 5: fini() on host_iter is independent of plan_iter's own * lifecycle -- finiing it here must not disturb plan_iter, which is * still fini()'d separately, afterward, below. */ host_iter->fini(host_iter); TEST_ASSERT_NULL(host_iter->private); /* plan_iter is unaffected by host_iter->fini() -- still stably * exhausted (it already served its one plan above). Only now is its * own storage (and the `plan` pointer into it) released. */ TEST_ASSERT_NULL(plan_iter->next(plan_iter)); plan_iter->fini(plan_iter); TEST_ASSERT_NULL(plan_iter->private); } /* * Req 3 AC 4 / Req 4 AC 2 composition shape: a fresh xce_host_iter_t * instance is constructed from the SAME plan once per bucket, each a * fully independent instance -- the exact plan -> bucket -> host_iter * nesting requirements.md's Composition Algorithm pseudocode describes * for allocate_primary(), exercised here end to end against the stub * implementations (a 2-bucket plan, each bucket getting its own fresh * host_iter instance that is init()'d and fini()'d independently). */ static void test_fresh_host_iter_per_bucket_composes_with_one_plan(void) { int bucket_slots[2] = { 2, 1 }; xce_alloc_plan_t two_bucket_plan = { .nbuckets = 2, .bucket_slots = bucket_slots }; int b; for (b = 0; b < two_bucket_plan.nbuckets; b++) { xce_host_iter_t host_iter_storage; xce_host_iter_t* host_iter = &host_iter_storage; xce_sched_worker_t* w; memset(host_iter, 0, sizeof(*host_iter)); host_iter->init = stub_host_iter_init; host_iter->next = stub_host_iter_next; host_iter->fini = stub_host_iter_fini; /* A brand-new init() per bucket -- never one instance reused * across the loop (Req 3's "fresh per bucket" requirement). */ TEST_ASSERT_EQUAL_INT( 0, host_iter->init(host_iter, &two_bucket_plan, NULL)); w = host_iter->next(host_iter); TEST_ASSERT_NOT_NULL(w); host_iter->fini(host_iter); TEST_ASSERT_NULL(host_iter->private); } } int main(void) { UNITY_BEGIN(); RUN_TEST(test_plan_iter_stub_produces_one_plan_then_stable_null); RUN_TEST(test_host_iter_stub_produces_one_worker_then_stable_null); RUN_TEST(test_fresh_host_iter_per_bucket_composes_with_one_plan); return UNITY_END(); }