feat(scheduler-sim-diagnostics): add fmt_limit_units() sibling (task 8.1)

Adds fmt_limit_units(buf, len, limit, units_mode) to src/lib/xcelimit.c,
a units-mode-aware sibling of fmt_limit() following the
fmt_jobid()/fmt_jobid_ex() extended-sibling pattern. Reuses fmt_limit()'s
exact consumer-dump loop untouched; only the final value line branches:
percent limits always render bare "%f%%" regardless of mode, byte-valued
(or "swp"-special-cased) absolute values render via
xce_format_units_value() (task 7), everything else keeps the
unconditional "%f".

fmt_limit() itself is now a one-line XCE_UNITS_RAW wrapper around
fmt_limit_units() -- provably byte-identical to its prior output for
every input shape, so its 5 existing direct unit tests needed zero
changes (the regression proof this refactor relies on).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-20 05:09:09 +00:00
parent eb23c158ae
commit 9f54c5753f
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 362 additions and 5 deletions

View file

@ -360,12 +360,299 @@ static void test_match_limit2(void)
destroy(umgr);
}
/* ---------------------------------------------------------------------
* fmt_limit_units() -- scheduler-sim-diagnostics spec, Requirement 6,
* task 8.1. fmt_limit() itself becomes a one-line XCE_UNITS_RAW wrapper
* around this new sibling entry point; the five tests above (unmodified)
* are the regression proof that the wrapper is byte-identical to
* fmt_limit()'s pre-existing output. These new tests cover
* fmt_limit_units()'s own additional behavior: byte-valued resources
* rendered per mode, percentage-based and non-byte-valued resources left
* unaffected by mode, and the "swp" MB->bytes special case mirroring
* repl_format_numeric_resource_value()'s existing convention.
* ------------------------------------------------------------------- */
static void test_fmt_limit_units_byte_valued_resource_renders_per_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_mem",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 0,
/* 1 GiB, exactly representable as float --
* avoids float-rounding ambiguity in the
* expected-string literals below. */
.val = 1073741824.0 }
};
char buf[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_RAW);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1073741824.000000\n"),
"RAW mode must render the bare %f value for a byte-valued resource");
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_HUMAN);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1.00 GB\n"),
"HUMAN mode must render the human-scaled value alone");
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_BOTH);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1073741824.000000 (1.00 GB)\n"),
"BOTH mode must render raw and human forms together");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_percent_limit_ignores_units_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_mem_pct",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 1, .val = 80.0 }
};
char buf[1024];
xce_units_mode_t modes[] = { XCE_UNITS_RAW, XCE_UNITS_HUMAN,
XCE_UNITS_BOTH };
size_t i;
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
for (i = 0; i < (sizeof(modes) / sizeof(modes[0])); i++) {
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, modes[i]);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 80.000000%\n"),
"a percentage-based limit's value must render identically "
"regardless of units_mode (Requirement 6 AC3) -- a percentage "
"is never byte-scaled");
}
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_non_byte_valued_resource_ignores_units_mode(
void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_slots",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "slots", .percent = 0, .val = 3.0 }
};
char buf[1024];
xce_units_mode_t modes[] = { XCE_UNITS_RAW, XCE_UNITS_HUMAN,
XCE_UNITS_BOTH };
size_t i;
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
for (i = 0; i < (sizeof(modes) / sizeof(modes[0])); i++) {
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, modes[i]);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nslots = 3.000000\n"),
"a non-byte-valued resource's value must render identically "
"regardless of units_mode (Requirement 6 AC3)");
}
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_swp_resource_applies_mb_to_bytes_conversion(
void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_swp",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "swp", .percent = 0,
/* 1024 MB == 1 GiB once converted to bytes */
.val = 1024.0 }
};
char buf[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_HUMAN);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nswp = 1.00 GB\n"),
"swp's wire unit is MB, so fmt_limit_units() must apply the same "
"MB-to-bytes conversion repl_format_numeric_resource_value() "
"already applies before scaling for display");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_wrapper_matches_fmt_limit_units_raw_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc1, rc2;
xce_limit_config_t limit_config = {
.name = "limit_mem_wrapper",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 0,
.val = 1073741824.0 }
};
char buf1[1024];
char buf2[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc1 = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc1 == 1);
memset(buf1, 0x0, sizeof(buf1));
memset(buf2, 0x0, sizeof(buf2));
rc1 = fmt_limit(buf1, sizeof(buf1) - 1, limit);
rc2 = fmt_limit_units(buf2, sizeof(buf2) - 1, limit, XCE_UNITS_RAW);
TEST_ASSERT_TRUE(rc1 > 0);
TEST_ASSERT_TRUE(rc2 > 0);
TEST_ASSERT_EQUAL_STRING_MESSAGE(
buf2, buf1,
"fmt_limit() must remain a byte-identical XCE_UNITS_RAW wrapper "
"around fmt_limit_units()");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
void test_limit(void)
{
printf("\n>> limit test\n\n");
xstring_table_init(139);
RUN_TEST(test_build_consumer1);
RUN_TEST(test_build_consumer2);
RUN_TEST(test_build_consumer_empty_config_defaults_to_all);
@ -374,6 +661,12 @@ void test_limit(void)
RUN_TEST(test_match_limit_ok2);
RUN_TEST(test_match_limit1);
RUN_TEST(test_match_limit2);
RUN_TEST(test_fmt_limit_units_byte_valued_resource_renders_per_mode);
RUN_TEST(test_fmt_limit_units_percent_limit_ignores_units_mode);
RUN_TEST(
test_fmt_limit_units_non_byte_valued_resource_ignores_units_mode);
RUN_TEST(test_fmt_limit_units_swp_resource_applies_mb_to_bytes_conversion);
RUN_TEST(test_fmt_limit_wrapper_matches_fmt_limit_units_raw_mode);
xstring_table_fini();
}