/* * test_repl_resource_formatting.c - RED-phase contract test for the REPL's * resource-value byte/megabyte classification and adaptive human-scaling * helpers (scheduler-sim-repl-verbosity spec, task 7.1, Requirement 6 * "Human-Readable Resource Value Formatting", ACs 6.1, 6.2, 6.3). * * THIS TASK (7.1) IS RED-PHASE ONLY: neither `repl_is_bytes_valued_resource()` * nor `repl_format_bytes_human()` exists anywhere in this codebase yet -- * task 7.2 (a separate, later task) implements them. Building this file is * therefore expected to FAIL AT LINK TIME (undefined reference to both * symbols), not to fail a runtime assertion -- that link failure IS this * task's Observable evidence per tasks.md. Once task 7.2 lands real * definitions, this same file (unmodified) becomes the GREEN-phase proof. * `set units` parsing (also part of Requirement 6.2/6.3) is covered * separately in test_repl_snapshot_params.c, alongside that file's existing * `set speed`/`set output`/`set verify` tests -- this file covers ONLY the * two pure classification/scaling functions. * * Declaring the functions under test: same rationale as * test_repl_output_compression.c's own header comment (task 1.1 precedent, * cited by design.md's Testing Strategy section as this file's pattern to * mirror). design.md's "Resource Value Formatter" component documents both * functions as pure, self-contained formatters with "no dependency on * sim/core/repl state" (Contracts: Service [x] / State [x] -- the [x] on * State refers to the SEPARATE `units_mode` field these two functions never * touch, not to any state of their own). Rather than white-box #include-ing * repl.c (which would drag in repl_internal.h's other extern * cross-references to repl_cmd_break/repl_cmd_info/repl_cmd_set/... in * sibling files, producing a pile of unrelated undefined-reference errors * that would bury the two this task actually wants to prove), this file * declares both functions locally as plain `extern`s with the exact * signatures design.md's Service Interface gives. Once task 7.2 gives them a * shared declaration in repl_internal.h, these local prototypes become * redundant but harmless (identical signatures) -- meson.build links this * test against the real scheduler_srcs object list (not a textual * #include), the same technique test_repl_output_compression.c's own * meson.build entry already established for this exact "real production * dependency surface, avoid textual inclusion" situation. * * Evidence for the classification list and the swp special case (design.md * cites these as the exact grounding for its "confirmed canonical-bytes"/ * "confirmed canonical-megabytes" claims, re-verified directly against * source for this task): * - src/worker/src/node_information.c: node_info_get_mem()'s doc comment * ("physical memory in bytes (canonical internal/wire unit -- same * convention as the maxmem host attribute)"), node_info_get_tmp()'s doc * comment (same canonical-bytes convention), and node_info_get_swap()'s * doc comment ("currently available virtual memory (swap space) in MB" * -- megabytes, NOT bytes, the one deliberate exception). * - src/worker/src/xcecd.c's init_host_attrs() bug-1554 comment (line * ~353): "host_attrs.maxswap"/"host_attrs.maxtmp" are set in the SAME * canonical unit as "maxmem, mem, tmp" -- i.e. bytes -- confirming * maxswap/maxtmp belong in the bytes-valued list alongside maxmem/mem/ * tmp, unlike their un-prefixed `swp` load-index counterpart. * - src/controller/testsuite/test.c's is_bytes_valued_resource()/ * format_bytes_human() (design.md's cited precedent): same 5-name list * { "maxmem", "mem", "tmp", "maxswap", "maxtmp" }, same "divide by 1024 * while >= 1024.0, cap at PB (6 units)" adaptive-scaling algorithm, same * "%.2f %s" render format (e.g. "20.56 GB") -- this file's expected * values below are computed against that identical algorithm. * * Assumed output contract (design.md's Service Interface documents the * classification list and the scaling algorithm, but pins one literal * example only -- "20.56 GB" -- this header comment fixes the rest of the * literal format these tests rely on, for task 7.2 to satisfy): * - repl_is_bytes_valued_resource(name) returns true for EXACTLY "mem", * "tmp", "maxmem", "maxtmp", "maxswap" and false for every other name, * including "swp" (special-cased separately, see below), an unrelated * unitless resource such as "r1m" (a 1-minute load average -- cited by * test.c's own is_bytes_valued_resource() doc comment as a deliberate * non-member alongside cpuf/r15s), and an unknown/custom name. * - repl_format_bytes_human(bytes, buf, bufsz) writes " " with * `val` rendered via "%.2f" (always exactly two decimal places, e.g. * "1.00 KB", never "1 KB" or "1.0 KB") and `UNIT` one of B/KB/MB/GB/TB/ * PB, choosing the smallest unit `bytes` fits in a value < 1024 for (or * PB once no larger unit remains, per test.c's own cap-at-PB behavior). * - `swp`'s wire value is in megabytes (node_info_get_swap()'s own doc * comment); to render it on the SAME adaptive B/KB/MB/GB/TB/PB scale a * byte-native resource uses, design.md's own "Resource Value Formatter" * Responsibilities section requires converting it to bytes (x 1024.0 x * 1024.0) BEFORE calling repl_format_bytes_human() -- exercised directly * below via the same two public pure functions, since the conversion * itself is a one-line caller-side multiplication, not a third helper * function design.md declares anywhere. */ #include #include #include #include /* * repl_is_bytes_valued_resource / repl_format_bytes_human (task 7.2, NOT YET * IMPLEMENTED -- see this file's own header comment above for why these are * local externs rather than a white-box #include). Signatures copied * verbatim from design.md's "Resource Value Formatter" Service Interface. */ extern bool repl_is_bytes_valued_resource(const char* name); extern void repl_format_bytes_human(double bytes, char* buf, size_t bufsz); void setUp(void) { return; } void tearDown(void) { return; } /* ---------------------------------------------------------------------- */ /* Test 1 (AC 6.1): the classifier returns true for EXACTLY the five */ /* confirmed canonical-bytes resource names. */ /* ---------------------------------------------------------------------- */ static void test_bytes_valued_classifier_true_for_canonical_five(void) { TEST_ASSERT_TRUE_MESSAGE(repl_is_bytes_valued_resource("mem"), "mem's wire unit is bytes " "(node_info_get_mem() doc comment)"); TEST_ASSERT_TRUE_MESSAGE(repl_is_bytes_valued_resource("tmp"), "tmp's wire unit is bytes " "(node_info_get_tmp() doc comment)"); TEST_ASSERT_TRUE_MESSAGE(repl_is_bytes_valued_resource("maxmem"), "maxmem's wire unit is bytes " "(xcecd.c init_host_attrs() bug-1554 comment)"); TEST_ASSERT_TRUE_MESSAGE(repl_is_bytes_valued_resource("maxtmp"), "maxtmp's wire unit is bytes " "(xcecd.c init_host_attrs() bug-1554 comment)"); TEST_ASSERT_TRUE_MESSAGE(repl_is_bytes_valued_resource("maxswap"), "maxswap's wire unit is bytes -- unlike its " "un-prefixed `swp` load-index counterpart " "(xcecd.c init_host_attrs() bug-1554 comment)"); printf("test_bytes_valued_classifier_true_for_canonical_five: PASS\n"); } /* ---------------------------------------------------------------------- */ /* Test 2 (AC 6.1): the classifier returns false for `swp` (a deliberate */ /* special case, megabytes not bytes), an unrelated unitless resource, and */ /* an unknown/custom name. */ /* ---------------------------------------------------------------------- */ static void test_bytes_valued_classifier_false_for_swp_unitless_and_unknown( void) { TEST_ASSERT_FALSE_MESSAGE( repl_is_bytes_valued_resource("swp"), "swp is deliberately NOT in the bytes-valued list -- its wire unit " "is megabytes (node_info_get_swap() doc comment), handled via a " "caller-side MB->bytes conversion instead (see test 5 below)"); TEST_ASSERT_FALSE_MESSAGE( repl_is_bytes_valued_resource("r1m"), "r1m (1-minute load average) is a unitless factor, not a " "byte-denominated resource (test.c's own is_bytes_valued_resource() " "doc comment names it as a deliberate non-member)"); TEST_ASSERT_FALSE_MESSAGE( repl_is_bytes_valued_resource("gpu_slots"), "an unknown/custom resource name must not be misclassified as " "bytes-valued -- this is a small, explicit, hardcoded list, not a " "heuristic"); printf("test_bytes_valued_classifier_false_for_swp_unitless_and_unknown: " "PASS\n"); } /* ---------------------------------------------------------------------- */ /* Test 3 (AC 6.2): adaptive scaling across the full B/KB/MB/GB/TB/PB unit */ /* range, at exact unit boundaries -- the 1024-byte boundary must render as */ /* "1.00 KB", never "1024.00 B" (design.md's own literal example of the */ /* algorithm's intent). */ /* ---------------------------------------------------------------------- */ static void test_format_bytes_human_scales_across_full_unit_range_at_boundaries( void) { char buf[64]; memset(buf, 0, sizeof(buf)); repl_format_bytes_human(0.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("0.00 B", buf); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1023.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING_MESSAGE( "1023.00 B", buf, "just below the KB boundary must stay in B"); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING_MESSAGE( "1.00 KB", buf, "exactly 1024 bytes must render as '1.00 KB', not '1024.00 B'"); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1024.0 * 1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("1.00 MB", buf); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1024.0 * 1024.0 * 1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("1.00 GB", buf); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1024.0 * 1024.0 * 1024.0 * 1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("1.00 TB", buf); memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("1.00 PB", buf); /* PB is the largest unit -- a value that would nominally scale past PB * stays capped there instead of inventing a further unit (test.c's own * "cap at PB" precedent, design.md's "cap at PB" algorithm text). */ memset(buf, 0, sizeof(buf)); repl_format_bytes_human( 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING_MESSAGE( "1024.00 PB", buf, "a value past the PB range must stay capped at PB, not invent a " "further unit"); printf("test_format_bytes_human_scales_across_full_unit_range_at_" "boundaries: PASS\n"); } /* ---------------------------------------------------------------------- */ /* Test 4 (AC 6.2): non-boundary, fractional values render with exactly two */ /* decimal places within their chosen unit (design.md's own literal example */ /* is a non-boundary value: "20.56 GB"). */ /* ---------------------------------------------------------------------- */ static void test_format_bytes_human_fractional_values_render_two_decimals( void) { char buf[64]; /* 1.5 MB, exactly representable in double (1.5 * 1048576 = 1572864.0) -- * a clean fractional value within one unit, not a unit-boundary value. */ memset(buf, 0, sizeof(buf)); repl_format_bytes_human(1572864.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("1.50 MB", buf); /* 500 bytes -- fractional-free but still exercises the fixed "%.2f" * two-decimal render at the smallest (B) unit. */ memset(buf, 0, sizeof(buf)); repl_format_bytes_human(500.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("500.00 B", buf); /* 5,000,000,000 bytes / 1024^3 = 4.656612873077393... GB, rounds via * "%.2f" to "4.66 GB" -- an arbitrary, non-power-of-2 value exercising * real rounding, not just clean multiples of 1024. */ memset(buf, 0, sizeof(buf)); repl_format_bytes_human(5000000000.0, buf, sizeof(buf)); TEST_ASSERT_EQUAL_STRING("4.66 GB", buf); printf("test_format_bytes_human_fractional_values_render_two_decimals: " "PASS\n"); } /* ---------------------------------------------------------------------- */ /* Test 5 (AC 6.1): swp's value, converted MB->bytes by the caller, renders */ /* on the SAME adaptive scale a byte-native resource of equivalent */ /* magnitude would -- and skipping the conversion (feeding the raw MB */ /* number straight to the byte-scaling formatter) produces a visibly wrong */ /* result, proving the conversion step is load-bearing. */ /* ---------------------------------------------------------------------- */ static void test_swp_value_converted_mb_to_bytes_renders_on_same_adaptive_scale( void) { char buf_converted[64]; char buf_byte_native[64]; char buf_unconverted[64]; double swp_raw_mb = 2048.0; /* swp's wire value, in MB */ double swp_bytes = swp_raw_mb * 1024.0 * 1024.0; /* == 2 GiB in bytes */ double byte_native_equivalent = 2.0 * 1024.0 * 1024.0 * 1024.0; TEST_ASSERT_FALSE_MESSAGE( repl_is_bytes_valued_resource("swp"), "swp must never be classified bytes-valued -- the MB->bytes " "conversion is a caller-side step taken BECAUSE the classifier " "correctly says false, not a classifier behavior itself"); memset(buf_converted, 0, sizeof(buf_converted)); repl_format_bytes_human(swp_bytes, buf_converted, sizeof(buf_converted)); TEST_ASSERT_EQUAL_STRING_MESSAGE( "2.00 GB", buf_converted, "swp's 2048 MB, converted to bytes (x 1024.0 x 1024.0) before " "scaling, must render as '2.00 GB'"); /* A byte-native resource of the identical magnitude (e.g. maxmem at * exactly 2 GiB) must render IDENTICALLY -- same function, same units, * no swp-specific quirk in the scaling output itself. */ memset(buf_byte_native, 0, sizeof(buf_byte_native)); repl_format_bytes_human(byte_native_equivalent, buf_byte_native, sizeof(buf_byte_native)); TEST_ASSERT_EQUAL_STRING_MESSAGE( buf_byte_native, buf_converted, "swp's converted rendering must be indistinguishable from a " "byte-native resource of the same magnitude"); /* Contrast: feeding swp's RAW megabyte number straight into the * byte-scaling formatter without converting first (the bug this * conversion step exists to prevent) produces a materially different, * wrong result -- 2048 misread as bytes is only 2 KB, not 2 GB. */ memset(buf_unconverted, 0, sizeof(buf_unconverted)); repl_format_bytes_human(swp_raw_mb, buf_unconverted, sizeof(buf_unconverted)); TEST_ASSERT_EQUAL_STRING_MESSAGE( "2.00 KB", buf_unconverted, "sanity check: without the MB->bytes conversion, swp's raw value " "misreads three orders of magnitude too small -- confirms the " "conversion in the test above is load-bearing, not incidental"); printf("test_swp_value_converted_mb_to_bytes_renders_on_same_adaptive_" "scale: PASS\n"); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_bytes_valued_classifier_true_for_canonical_five); RUN_TEST(test_bytes_valued_classifier_false_for_swp_unitless_and_unknown); RUN_TEST( test_format_bytes_human_scales_across_full_unit_range_at_boundaries); RUN_TEST(test_format_bytes_human_fractional_values_render_two_decimals); RUN_TEST( test_swp_value_converted_mb_to_bytes_renders_on_same_adaptive_scale); return UNITY_END(); }