feat(scheduler-sim-diagnostics): implement info limit name/all resolution
repl_info_limit() (repl_info.c, not yet wired into repl_cmd_info() -- that's task 4) resolves "all" vs a specific name against limit_get_configured_limits(): a NULL/empty handle on the all path reports "no limits configured" (Req 1.7); a NULL handle or key_find() miss on the named path reports "no such limit '<name>'" (Req 1.3) and never collapses into the all-only message. New white-box test_repl_limit_table.c exercises both paths directly, including a dedicated regression guard for the NULL-scoping rule. Making repl_info.c genuinely call limit_get_configured_limits() broke the link for ~24 other white-box test files that #include repl_info.c without also linking plugins/limit.c -- fixed with the same link-satisfying-stub idiom this file family already uses elsewhere (all return NULL, verified behavior- preserving since none of these fixtures register the limit plugin). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
b79a547a1e
commit
d3df8fb29f
28 changed files with 1098 additions and 1 deletions
621
src/scheduler/testsuite/test_repl_limit_table.c
Normal file
621
src/scheduler/testsuite/test_repl_limit_table.c
Normal file
|
|
@ -0,0 +1,621 @@
|
|||
/*
|
||||
* test_repl_limit_table.c - contract test for the interactive simulation
|
||||
* REPL's `info limit <name|all>` name/all resolution and absence messaging
|
||||
* (scheduler-sim-diagnostics spec, Requirement Set 1, task 2.1, Req 1.3,
|
||||
* 1.7, 4.1, 4.2).
|
||||
*
|
||||
* New white-box test file, sibling of test_repl_resource_table.c -- kept
|
||||
* separate rather than added to that file, per design.md's own explicit
|
||||
* build-wiring risk note (Implementation Notes, "REPL Limit Info Command"):
|
||||
* test_repl_resource_table.c white-box #includes repl_info.c but
|
||||
* deliberately does NOT link plugins/limit.c or resource.c ("resource.c,
|
||||
* not linked here"). This file needs the REAL plugins/limit.c (for the
|
||||
* real limit_get_configured_limits()/limit_init()/limit_update_config()
|
||||
* repl_info_limit() and this file's own fixtures call) -- not stubbed,
|
||||
* same bug-1762 precedent design.md cites for a different scheduler-sim-
|
||||
* repl-verbosity command that hit the identical class of undefined-
|
||||
* reference link failure.
|
||||
*
|
||||
* `repl_info_limit()` is NOT yet wired into repl_cmd_info() (that's task
|
||||
* 4) -- every test below calls it directly, not through repl_dispatch().
|
||||
*
|
||||
* White-box #include set: identical to test_repl_resource_table.c's own
|
||||
* (core.c, core_clock.c, core_events.c, core_session.c, decision.c,
|
||||
* plugin.c, plugins/builtin_fcfs.c, recording.c, recording_session.c,
|
||||
* repl.c, repl_fmt.c, repl_rollup.c, repl_break.c, repl_info.c,
|
||||
* repl_params.c, sim.c, sim_loop.c, sim_picker.c, sim_record.c,
|
||||
* sim_verify.c, snapshot.c, snapshot_load.c, snapshot_wire.c) PLUS the new
|
||||
* ../src/plugins/limit.c under test. task 2.1's own scope never reads live
|
||||
* usage/resource accounting (that's task 3), so resource.c is NOT linked
|
||||
* here either -- the same minimal link-satisfying stubs
|
||||
* test_limit_plugin.c already establishes for limit.c's unused
|
||||
* limit_job_election()/limit_prepare_allocation() hook bodies
|
||||
* (reset_sched_obj()/get_resvals()/put_resvals(), none of which task 2.1's
|
||||
* own code path ever calls) are reused verbatim here, confirmed sufficient
|
||||
* by actually attempting the build and reading the resulting undefined-
|
||||
* reference errors rather than guessing blindly, per design.md's own risk
|
||||
* note.
|
||||
*
|
||||
* Fixture strategy: unlike test_limit_plugin.c's own TU (which links
|
||||
* neither core.c nor plugin.c, so its fixture_init()/
|
||||
* fixture_register_plugin_in_core() hand-build a standalone core->plugins
|
||||
* kvmgr), this file links the REAL ../src/core.c, so xce_sched_core_init()
|
||||
* already creates a real, usable sim->core.plugins kvmgr on its own --
|
||||
* register_limit_plugin() (below) key_add()s a real, limit_init()'d
|
||||
* xce_sched_plugin_t directly into THAT kvmgr, and add_configured_limit()
|
||||
* drives the real limit_update_config() (plugins/limit.c) against it,
|
||||
* exactly the machinery Implementation Notes' "task 1" note predicted this
|
||||
* file would have available and confirms here.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <sys/wait.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "controller.capnp.h"
|
||||
#include "group.h"
|
||||
#include "kvmgr.h"
|
||||
#include "scheduler.h"
|
||||
#include "xce/types.h"
|
||||
#include "xcelimit.h"
|
||||
#include "xstring.h"
|
||||
|
||||
/* Pull in the real functions under test into this TU (white-box #include,
|
||||
* same convention as test_repl_resource_table.c/test_repl_round_rollup.c/
|
||||
* test_repl_event_echo.c). */
|
||||
#include "../src/core.c"
|
||||
#include "../src/core_clock.c"
|
||||
#include "../src/core_events.c"
|
||||
#include "../src/core_session.c"
|
||||
#include "../src/decision.c"
|
||||
#include "../src/plugin.c"
|
||||
#include "../src/plugins/builtin_fcfs.c"
|
||||
#include "../src/plugins/limit.c"
|
||||
#include "../src/recording.c"
|
||||
#include "../src/recording_session.c"
|
||||
#include "../src/repl.c"
|
||||
#include "../src/repl_fmt.c"
|
||||
#include "../src/repl_rollup.c"
|
||||
#include "../src/repl_break.c"
|
||||
#include "../src/repl_info.c"
|
||||
#include "../src/repl_params.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() now calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
#include "../src/sim_record.c"
|
||||
#include "../src/sim_verify.c"
|
||||
#include "../src/snapshot.c"
|
||||
#include "../src/snapshot_load.c"
|
||||
#include "../src/snapshot_wire.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
void kill_sched_worker(void* arg) { (void)arg; }
|
||||
void kill_sched_user(void* arg) { (void)arg; }
|
||||
void kill_sched_project(void* arg) { (void)arg; }
|
||||
void kill_sched_resval(xce_val_t* val) { (void)val; }
|
||||
|
||||
/* resource-accounting.md's reconciliation (resource.c, not linked here --
|
||||
* this file only #includes core_events.c, same as test_repl_resource_table.c).
|
||||
* core_events.c's XCE_CORE_EVENT_JOB_RUSAGE handler calls this whenever
|
||||
* job->accounts != NULL; link-satisfying stub only, same rationale as every
|
||||
* other stub in this block. */
|
||||
int sched_reconcile_job_resource_usage(xce_sched_core_t* c,
|
||||
xce_job_account_t* a,
|
||||
xce_job_rusages_t* r)
|
||||
{
|
||||
(void)c; (void)a; (void)r; return 0;
|
||||
}
|
||||
|
||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||
|
||||
int sched_fill_limit_requests(xce_sched_alloc_req_t* alloc_req,
|
||||
xlist_t* limits, xlist_t* rusages, int nhosts,
|
||||
int nslots)
|
||||
{
|
||||
(void)alloc_req;
|
||||
(void)limits;
|
||||
(void)rusages;
|
||||
(void)nhosts;
|
||||
(void)nslots;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_account_jobs(xce_sched_core_t* core)
|
||||
{
|
||||
(void)core;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Real reimplementation of schedobj.c's sched_resolve_job_picker -- same
|
||||
* copy test_repl_resource_table.c/test_repl_execution_control.c/
|
||||
* test_sim_alt_events.c/etc. each carry (schedobj.c is not linked here). */
|
||||
int sched_resolve_job_picker(xce_sched_core_t* core, xce_sched_plugin_t** out)
|
||||
{
|
||||
xce_sched_plugin_t* plugin = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if ((core == NULL) || (out == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*out = NULL;
|
||||
|
||||
if (core->job_pick_plugin == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
lock(core->plugins, RTTI_LOCK_READ);
|
||||
rc = key_find(core->plugins, core->job_pick_plugin, (void**)&plugin);
|
||||
unlock(core->plugins);
|
||||
|
||||
if (rc <= 0) {
|
||||
return (rc < 0) ? rc : -ENOENT;
|
||||
}
|
||||
|
||||
if ((plugin == NULL) || (plugin->picker_iface == NULL)
|
||||
|| (plugin->picker_iface->next_job == NULL)
|
||||
|| (plugin->picker_iface->begin_session == NULL)
|
||||
|| (plugin->picker_iface->end_session == NULL)
|
||||
|| (plugin->picker_iface->on_dispatch == NULL)
|
||||
|| (plugin->picker_iface->on_pend == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*out = plugin;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* plugins/limit.c's limit_job_election()/limit_prepare_allocation() (never
|
||||
* exercised by any test in this file -- task 2.1's own scope never touches
|
||||
* usage/accounting) reference reset_sched_obj() (../src/schedobj.c) and
|
||||
* get_resvals()/put_resvals() (../src/resource.c), neither of which is part
|
||||
* of this white-box TU. Stubbed as no-ops purely to satisfy the linker --
|
||||
* same pattern test_limit_plugin.c already establishes for the identical
|
||||
* symbols. */
|
||||
void reset_sched_obj(xce_sched_obj_t* obj)
|
||||
{
|
||||
(void)obj;
|
||||
}
|
||||
|
||||
int get_resvals(zarray_t* array, int nres, int* idxs, float* vals)
|
||||
{
|
||||
(void)array;
|
||||
(void)nres;
|
||||
(void)idxs;
|
||||
(void)vals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int put_resvals(zarray_t* array, int nres, int* idxs, float* vals)
|
||||
{
|
||||
(void)array;
|
||||
(void)nres;
|
||||
(void)idxs;
|
||||
(void)vals;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* stdout-capture helper -- duplicated from test_repl_resource_table.c */
|
||||
/* (same dup()/dup2()-around-STDOUT_FILENO technique that file's own */
|
||||
/* header comment establishes), per this file family's own "duplicate, */
|
||||
/* don't share" convention. */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
static char* capture_stdout_around(void (*fn)(void))
|
||||
{
|
||||
char tmp[] = "/tmp/xce_repllimtbl_stdoutcap_XXXXXX";
|
||||
int fd = mkstemp(tmp);
|
||||
int saved_stdout;
|
||||
off_t len;
|
||||
char* buf;
|
||||
|
||||
TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp for stdout capture must "
|
||||
"succeed");
|
||||
|
||||
fflush(stdout);
|
||||
saved_stdout = dup(STDOUT_FILENO);
|
||||
TEST_ASSERT_TRUE_MESSAGE(saved_stdout >= 0, "dup(STDOUT_FILENO) must "
|
||||
"succeed");
|
||||
TEST_ASSERT_TRUE_MESSAGE(dup2(fd, STDOUT_FILENO) >= 0,
|
||||
"dup2 redirect of STDOUT_FILENO must succeed");
|
||||
|
||||
fn();
|
||||
|
||||
fflush(stdout);
|
||||
dup2(saved_stdout, STDOUT_FILENO);
|
||||
close(saved_stdout);
|
||||
|
||||
len = lseek(fd, 0, SEEK_CUR);
|
||||
TEST_ASSERT_TRUE(len >= 0);
|
||||
|
||||
buf = (char*)calloc(1, (size_t)len + 1);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
TEST_ASSERT_TRUE(lseek(fd, 0, SEEK_SET) == 0);
|
||||
if (len > 0) {
|
||||
ssize_t nread = read(fd, buf, (size_t)len);
|
||||
TEST_ASSERT_TRUE(nread == len);
|
||||
}
|
||||
close(fd);
|
||||
unlink(tmp);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* Captures the output of calling repl_info_limit(sim, arg) DIRECTLY --
|
||||
* task 2.1's own scope calls it directly, not through repl_dispatch()
|
||||
* (that wiring is task 4's job). */
|
||||
static xce_sched_sim_driver_t* g_direct_sim;
|
||||
static const char* g_direct_arg;
|
||||
|
||||
static void run_info_limit_for_capture(void)
|
||||
{
|
||||
repl_info_limit(g_direct_sim, g_direct_arg);
|
||||
}
|
||||
|
||||
static char* capture_info_limit(xce_sched_sim_driver_t* sim, const char* arg)
|
||||
{
|
||||
g_direct_sim = sim;
|
||||
g_direct_arg = arg;
|
||||
return capture_stdout_around(run_info_limit_for_capture);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Limit configuration fixture helpers */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* register_limit_plugin - registers a real, limit_init()'d xce_sched_plugin_t
|
||||
* directly into `sim`'s own core->plugins kvmgr (xce_sched_core_init() --
|
||||
* already called on `sim` by every test below -- creates that kvmgr for
|
||||
* real, since this TU links ../src/core.c), so
|
||||
* limit_get_configured_limits(&(sim->core))'s key_find(core->plugins,
|
||||
* "xce_sched_limit", ...) genuinely finds it -- the exact real lookup path
|
||||
* repl_info_limit() drives.
|
||||
*
|
||||
* `plugin`/`group_sched` are caller-owned (stack) storage, not static
|
||||
* globals, so each test gets its own independent fixture. `group_sched` is
|
||||
* a throwaway xce_scheduler_t existing purely so limit_update_config()
|
||||
* (called separately by add_configured_limit() below) has real group
|
||||
* managers to resolve each consumer's "all"-group fallback from
|
||||
* (build_limit()/build_consumer(), xcelimit.c) -- the built xce_limit_t/
|
||||
* xce_group_t values it produces are owned by plugin->data's own
|
||||
* privdata->limits kvmgr, not by group_sched, so group_sched need not be
|
||||
* (and is not) sim's own core. Same minimal group-manager technique
|
||||
* test_limit_plugin.c's own fixture_init() already establishes
|
||||
* (groupmgr_init() x4), reused here rather than re-derived, per
|
||||
* Implementation Notes' own "task 1" note.
|
||||
*/
|
||||
static void register_limit_plugin(xce_sched_sim_driver_t* sim,
|
||||
xce_sched_plugin_t* plugin,
|
||||
xce_scheduler_t* group_sched)
|
||||
{
|
||||
int rc;
|
||||
|
||||
memset(group_sched, 0, sizeof(*group_sched));
|
||||
groupmgr_init(&(group_sched->core.ugmgr), 4, 4);
|
||||
groupmgr_init(&(group_sched->core.qgmgr), 4, 4);
|
||||
groupmgr_init(&(group_sched->core.pgmgr), 4, 4);
|
||||
groupmgr_init(&(group_sched->core.hgmgr), 4, 4);
|
||||
TEST_ASSERT_NOT_NULL(group_sched->core.ugmgr);
|
||||
TEST_ASSERT_NOT_NULL(group_sched->core.qgmgr);
|
||||
TEST_ASSERT_NOT_NULL(group_sched->core.pgmgr);
|
||||
TEST_ASSERT_NOT_NULL(group_sched->core.hgmgr);
|
||||
|
||||
memset(plugin, 0, sizeof(*plugin));
|
||||
plugin->name = "xce_sched_limit";
|
||||
plugin->sched = group_sched;
|
||||
|
||||
rc = limit_init(plugin);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_NOT_NULL(plugin->data);
|
||||
|
||||
rc = key_add(sim->core.plugins, plugin->name, (void*)plugin);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
}
|
||||
|
||||
/* A minimal, heap-allocated xce_limit_config_t -- identical shape to
|
||||
* test_limit_plugin.c's own make_limit_config()/free_limit_config()
|
||||
* (duplicated here per this file family's own "duplicate, don't share"
|
||||
* convention): no per-consumer groups, so build_limit()'s build_consumer()
|
||||
* falls back to get_group(mgr, "all") for every consumer. `name` is
|
||||
* expected to be a string literal (build_limit() strdup()s it). */
|
||||
static xce_limit_config_t* make_limit_config(const char* name)
|
||||
{
|
||||
xce_limit_config_t* cfg = (xce_limit_config_t*)calloc(1, sizeof(*cfg));
|
||||
xce_limit_val_t* val = (xce_limit_val_t*)calloc(1, sizeof(*val));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(cfg);
|
||||
TEST_ASSERT_NOT_NULL(val);
|
||||
|
||||
val->resname = "mem";
|
||||
val->percent = 0;
|
||||
val->val = 10.0;
|
||||
|
||||
cfg->name = (char*)name;
|
||||
cfg->val = val;
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static void free_limit_config(xce_limit_config_t* cfg)
|
||||
{
|
||||
FREEUP(cfg->val);
|
||||
FREEUP(cfg);
|
||||
}
|
||||
|
||||
/* Drives the real limit_update_config() (plugins/limit.c) to configure ONE
|
||||
* limit named `name` on `plugin` -- the exact production entry point a
|
||||
* scheduler config update reaches this plugin through, not a hand-rolled
|
||||
* xce_limit_t. */
|
||||
static void add_configured_limit(xce_sched_plugin_t* plugin, const char* name)
|
||||
{
|
||||
xce_limit_config_t* limits[1];
|
||||
xce_conf4sched_t config;
|
||||
int rc;
|
||||
|
||||
limits[0] = make_limit_config(name);
|
||||
|
||||
memset(&config, 0, sizeof(config));
|
||||
config.nlimits = 1;
|
||||
config.limits = limits;
|
||||
|
||||
rc = limit_update_config(plugin, &config);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
free_limit_config(limits[0]);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Tests */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* Requirement 1.7: `info limit all` against a fixture with zero configured
|
||||
* limits (the limit plugin was never registered at all -- the realistic
|
||||
* "nothing configured" scenario per design.md's Error Handling) must
|
||||
* report that no limits are configured.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_all_reports_no_limits_configured_when_plugin_never_registered(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
captured = capture_info_limit(&sim, "all");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "no limits configured"),
|
||||
"Requirement 1.7: 'info limit all' against a fixture with zero "
|
||||
"configured limits must report that no limits are configured");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_all_reports_no_limits_configured_when_plugin_"
|
||||
"never_registered: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Requirement 1.7 / design.md's Error Handling section: a REGISTERED
|
||||
* plugin with zero configured limits (privdata->limits is a real, non-NULL,
|
||||
* empty kvmgr) must ALSO collapse to "no limits configured" on the `all`
|
||||
* path -- not an empty listing with no explanation.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_all_reports_no_limits_configured_when_plugin_registered_with_zero_limits(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
/* Deliberately no add_configured_limit() call -- plugin registered,
|
||||
* privdata->limits real but empty. */
|
||||
|
||||
captured = capture_info_limit(&sim, "all");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "no limits configured"),
|
||||
"a registered plugin with zero configured limits must also report "
|
||||
"'no limits configured' on the 'all' path");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_all_reports_no_limits_configured_when_plugin_"
|
||||
"registered_with_zero_limits: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Positive-path proof for the `all` resolution: once at least one limit is
|
||||
* genuinely configured, the Requirement 1.7 message must NOT appear --
|
||||
* proves the all-path iteration reaches the real, non-empty handle rather
|
||||
* than misfiring the empty-case message unconditionally.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_all_does_not_report_no_limits_configured_when_limits_are_configured(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
add_configured_limit(&plugin, "ug1_slots");
|
||||
|
||||
captured = capture_info_limit(&sim, "all");
|
||||
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no limits configured"),
|
||||
"'info limit all' must NOT report 'no limits configured' once at "
|
||||
"least one limit is genuinely configured");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_all_does_not_report_no_limits_configured_when_"
|
||||
"limits_are_configured: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Requirement 1.3 (this task's own Observable): a named lookup for a limit
|
||||
* name that is NOT configured, against a fixture with OTHER limits
|
||||
* configured, must report "no such limit" -- and must never report
|
||||
* Requirement 1.7's all-only message.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_reports_no_such_limit_for_unconfigured_name_against_fixture_with_other_limits(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
add_configured_limit(&plugin, "ug1_slots");
|
||||
|
||||
captured = capture_info_limit(&sim, "does_not_exist");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "no such limit 'does_not_exist'"),
|
||||
"Requirement 1.3: a named lookup for an unconfigured name, against "
|
||||
"a fixture with other limits configured, must report 'no such "
|
||||
"limit'");
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no limits configured"),
|
||||
"Requirement 1.7's message is reserved for the 'all' path -- a "
|
||||
"named-lookup miss must never say 'no limits configured'");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_reports_no_such_limit_for_unconfigured_"
|
||||
"name_against_fixture_with_other_limits: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* THE central scoping-regression guard (design.md's own explicit
|
||||
* correction, called out by this task's brief): a NULL
|
||||
* limit_get_configured_limits() handle on the NAMED path (the limit plugin
|
||||
* was never registered at all) must report Requirement 1.3's "no such
|
||||
* limit '<name>'" -- and must NEVER fall through to Requirement 1.7's
|
||||
* all-only "no limits configured" message, even though both conditions
|
||||
* stem from the identical underlying NULL handle.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_reports_no_such_limit_when_plugin_never_registered_not_no_limits_configured(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
captured = capture_info_limit(&sim, "foo");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "no such limit 'foo'"),
|
||||
"a NULL limit_get_configured_limits() handle on the NAMED path "
|
||||
"(plugin never registered) must report 'no such limit', never "
|
||||
"Requirement 1.7's all-only message");
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no limits configured"),
|
||||
"a NULL handle on the named path must never be treated as 'no "
|
||||
"limits configured' -- that message is exclusively the 'all' "
|
||||
"path's, per its own acceptance criterion's scoping");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_reports_no_such_limit_when_plugin_never_"
|
||||
"registered_not_no_limits_configured: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Positive-path proof for the named-lookup resolution: a name that IS
|
||||
* genuinely configured must never trigger the "no such limit" message --
|
||||
* proves key_find() resolves a real hit, not a false miss (would fail if
|
||||
* the resolution logic were broken or reversed).
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_does_not_report_no_such_limit_for_configured_name(void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
add_configured_limit(&plugin, "ug1_slots");
|
||||
|
||||
captured = capture_info_limit(&sim, "ug1_slots");
|
||||
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no such limit"),
|
||||
"a named lookup for a limit that genuinely IS configured must "
|
||||
"never report 'no such limit'");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_does_not_report_no_such_limit_for_"
|
||||
"configured_name: PASS\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(
|
||||
test_info_limit_all_reports_no_limits_configured_when_plugin_never_registered);
|
||||
RUN_TEST(
|
||||
test_info_limit_all_reports_no_limits_configured_when_plugin_registered_with_zero_limits);
|
||||
RUN_TEST(
|
||||
test_info_limit_all_does_not_report_no_limits_configured_when_limits_are_configured);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_reports_no_such_limit_for_unconfigured_name_against_fixture_with_other_limits);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_reports_no_such_limit_when_plugin_never_registered_not_no_limits_configured);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_does_not_report_no_such_limit_for_configured_name);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue