From 11fe6194ce7af7fe00fcd7e0fd78a027e495d34c Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Wed, 2 Sep 2026 16:45:59 +0000 Subject: [PATCH] feat(scheduler-sim-repl-verbosity): task 7.1 - RED-phase resource formatting tests Adds 5 pure-function tests in a new test_repl_resource_formatting.c for repl_is_bytes_valued_resource()/repl_format_bytes_human() (classification, adaptive B/KB/MB/GB/TB/PB scaling including exact boundary values, swp's MB->bytes conversion), and 3 tests in test_repl_snapshot_params.c for set units raw|human|both parsing (Requirements 6.1-6.3). All fail as expected: link failure for the not-yet-existing helpers, compile failure for the not-yet-existing units_mode field/enum. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd --- .../scheduler-sim-repl-verbosity/tasks.md | 2 +- src/scheduler/testsuite/meson.build | 33 ++ .../testsuite/test_repl_resource_formatting.c | 327 ++++++++++++++++++ .../testsuite/test_repl_snapshot_params.c | 134 +++++++ 4 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 src/scheduler/testsuite/test_repl_resource_formatting.c diff --git a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md index e5650a3c..0fbc7033 100644 --- a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md +++ b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md @@ -72,7 +72,7 @@ - _Boundary: REPL Prompt Context_ - [ ] 7. (P) Resource value classification, human-readable scaling, and the `set units` option -- [ ] 7.1 (P) Write failing tests for byte/megabyte classification, adaptive scaling, and `set units` parsing +- [x] 7.1 (P) Write failing tests for byte/megabyte classification, adaptive scaling, and `set units` parsing - Cover: the bytes-valued classifier returns true only for `mem`/`tmp`/`maxmem`/`maxtmp`/`maxswap` and false for `swp`, unitless resources, and unknown names; the scaling helper adaptively renders across B/KB/MB/GB/TB, including exact unit-boundary values; `swp`'s value is converted MB→bytes before scaling; `set units raw`/`human`/`both` each update the display-mode setting, defaulting to `both`; an unrecognized `set units` value is rejected the same way other unrecognized `set` parameters already are - Observable: the new test cases fail against the current codebase, which has neither the classifier/scaling helpers nor a `units` setting - _Requirements: 6.1, 6.2, 6.3_ diff --git a/src/scheduler/testsuite/meson.build b/src/scheduler/testsuite/meson.build index 86e58e6a..e6a1edd2 100644 --- a/src/scheduler/testsuite/meson.build +++ b/src/scheduler/testsuite/meson.build @@ -1126,6 +1126,36 @@ sched_repl_output_compression_test = executable( dependencies: [scheduler_deps, unity_dep], install: false) +# 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.1/6.2 "Human-Readable Resource Value Formatting"). +# repl_is_bytes_valued_resource()/repl_format_bytes_human() are pure, +# self-contained formatters (design.md's "Resource Value Formatter" +# component) that -- like repl_fmt_jobid_range() above -- do not yet exist +# anywhere in this codebase (task 7.2, a separate later task, implements +# them); this test file declares them as plain externs rather than +# #include-ing repl.c, same rationale as +# repl_output_compression_test_srcs above (repl.c's own dependency surface +# pulls in undefined cross-references to sibling files this test does not +# need). Same "link the real scheduler_srcs object list instead of textually +# including one" technique, for the identical reason: task 7.2 has not +# landed yet, so this target is expected to fail at LINK time (undefined +# reference to both symbols) until it does -- that failure is task 7.1's own +# Observable evidence. +repl_resource_formatting_test_srcs = ['test_repl_resource_formatting.c'] +foreach f : scheduler_srcs + if f != 'src/main.c' + repl_resource_formatting_test_srcs += ['../' + f] + endif +endforeach + +sched_repl_resource_formatting_test = executable( + 'sched_repl_resource_formatting_test', + repl_resource_formatting_test_srcs, + include_directories:[incdir], + dependencies: [scheduler_deps, unity_dep], + install: false) + # Tests for the scheduler's unified candidate-list connect/rejoin pipeline # (component-failover-adoption, task 3.1, boundary: SchedulerControllerClient). # Unlike the white-box #include-the-.c-file tests above, this target links @@ -1491,6 +1521,9 @@ test('scheduler repl equivalence test', test('scheduler repl output compression test', sched_repl_output_compression_test, suite: 'scheduler', env: ['ASAN_OPTIONS=detect_leaks=0']) +test('scheduler repl resource formatting test', + sched_repl_resource_formatting_test, suite: 'scheduler', + env: ['ASAN_OPTIONS=detect_leaks=0']) test('scheduler connect pipeline test', sched_scheduler_connect_test, suite: 'scheduler', env: ['ASAN_OPTIONS=detect_leaks=0']) diff --git a/src/scheduler/testsuite/test_repl_resource_formatting.c b/src/scheduler/testsuite/test_repl_resource_formatting.c new file mode 100644 index 00000000..f1d1db9e --- /dev/null +++ b/src/scheduler/testsuite/test_repl_resource_formatting.c @@ -0,0 +1,327 @@ +/* + * 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(); +} diff --git a/src/scheduler/testsuite/test_repl_snapshot_params.c b/src/scheduler/testsuite/test_repl_snapshot_params.c index edbd0061..ca1dcdb0 100644 --- a/src/scheduler/testsuite/test_repl_snapshot_params.c +++ b/src/scheduler/testsuite/test_repl_snapshot_params.c @@ -21,6 +21,17 @@ * one Observable that needs it" split test_repl_execution_control.c's own * header comment establishes. * + * `set units raw|human|both` (scheduler-sim-repl-verbosity spec, task 7.1, + * Requirement 6.2/6.3) is added below, alongside the existing `set speed`/ + * `set output`/`set verify` tests, per design.md's File Structure Plan + * ("test_repl_snapshot_params.c -- extended with `set units raw|human|both` + * parsing/validation tests ... alongside its existing `set speed`/`set + * output`/`set verify` tests"). THIS SLICE IS RED-PHASE ONLY: neither + * xce_sched_repl_state_s's `units_mode` field nor repl_cmd_set()'s `units` + * branch exists yet (task 7.2, a separate later task, adds both) -- see + * that test group's own header comment further below for the exact + * build-failure shape this is expected to produce today. + * * Fixture strategy and white-box #include set: identical to * test_repl_execution_control.c/test_repl_breakpoints_inspection.c (see * either file's own header comment) PLUS the new ../src/repl_params.c @@ -776,6 +787,126 @@ static void test_set_output_opens_writes_preamble_and_off_closes(void) printf("test_set_output_opens_writes_preamble_and_off_closes: PASS\n"); } +/* ---------------------------------------------------------------------- */ +/* Tests: `set units raw|human|both` (scheduler-sim-repl-verbosity spec, */ +/* task 7.1, Requirement 6.2/6.3). */ +/* */ +/* THESE THREE TESTS ARE RED-PHASE ONLY: neither xce_sched_repl_state_s's */ +/* `units_mode` field, the XCE_REPL_UNITS_RAW/HUMAN/BOTH enum constants, */ +/* nor repl_cmd_set()'s "units" branch exist anywhere in this codebase yet */ +/* -- task 7.2 (a separate, later task) adds all three. Referencing */ +/* `sim.repl_state->units_mode` and the enum constants below is therefore */ +/* expected to fail this file's COMPILATION (no member named 'units_mode', */ +/* undeclared identifiers) -- a build failure for the whole translation */ +/* unit, not merely a runtime assertion failure. That build failure IS this */ +/* slice's Observable evidence per tasks.md (task 7.1's own text: "the new */ +/* test cases fail against the current codebase, which has neither the */ +/* classifier/scaling helpers nor a `units` setting" -- a compile error is */ +/* a valid, and in this case the actual, form of "fail"). Once task 7.2 */ +/* lands the field/enum/branch, these same tests (unmodified) become the */ +/* GREEN-phase proof. */ +/* */ +/* Direct struct inspection (rs->units_mode) observes the setting rather */ +/* than routing through `info config`: today `info config` reports only */ +/* speed/output/verify/events_processed/current_ts/sessions + * (test_info_config_reports_current_parameters below), it does not report + * units_mode, and design.md's Traceability table never lists `info config` + * as a Resource Value Formatter dependency -- extending it is out of this + * task's scope (that belongs to task 8, the table-layout task). Direct + * struct access is the same technique + * test_set_speed_updates_shadow_and_core_atomics/ + * test_set_verify_toggles_flag already use for their own driver-state + * fields (sim.sim_speed_num, sim.verify) above. + */ + +static void test_set_units_raw_human_both_update_display_mode(void) +{ + xce_sched_sim_driver_t sim; + bool handled; + + memset(&sim, 0, sizeof(sim)); + sim.repl_state = repl_state_ensure(&sim); + TEST_ASSERT_NOT_NULL(sim.repl_state); + + handled = repl_cmd_set(&sim, "set units raw"); + TEST_ASSERT_FALSE_MESSAGE(handled, "`set` never hands control back"); + TEST_ASSERT_EQUAL_MESSAGE(XCE_REPL_UNITS_RAW, sim.repl_state->units_mode, + "`set units raw` must select the raw-only " + "display mode"); + + handled = repl_cmd_set(&sim, "set units human"); + TEST_ASSERT_FALSE(handled); + TEST_ASSERT_EQUAL_MESSAGE( + XCE_REPL_UNITS_HUMAN, sim.repl_state->units_mode, + "`set units human` must select the human-only display mode"); + + handled = repl_cmd_set(&sim, "set units both"); + TEST_ASSERT_FALSE(handled); + TEST_ASSERT_EQUAL_MESSAGE( + XCE_REPL_UNITS_BOTH, sim.repl_state->units_mode, + "`set units both` must select the combined raw+human display mode"); + + printf("test_set_units_raw_human_both_update_display_mode: PASS\n"); +} + +static void test_set_units_defaults_to_both_without_explicit_command(void) +{ + xce_sched_sim_driver_t sim; + + memset(&sim, 0, sizeof(sim)); + sim.repl_state = repl_state_ensure(&sim); + TEST_ASSERT_NOT_NULL(sim.repl_state); + + TEST_ASSERT_EQUAL_MESSAGE( + XCE_REPL_UNITS_BOTH, sim.repl_state->units_mode, + "absent an explicit `set units` command, units_mode must default " + "to `both` (Requirement 6.3) -- design.md's own State Management " + "section calls this out explicitly as the one field in this struct " + "that cannot rely on calloc's zero-initialization alone (unless " + "`both` is deliberately given enum value 0), so repl_state_ensure() " + "must set it explicitly"); + + printf( + "test_set_units_defaults_to_both_without_explicit_command: PASS\n"); +} + +static xce_sched_sim_driver_t g_units_invalid_sim; + +static void run_set_units_bogus(void) +{ + repl_cmd_set(&g_units_invalid_sim, "set units bogus"); +} + +static void test_set_units_invalid_value_rejected_like_existing_pattern(void) +{ + char* captured; + + memset(&g_units_invalid_sim, 0, sizeof(g_units_invalid_sim)); + g_units_invalid_sim.repl_state = repl_state_ensure(&g_units_invalid_sim); + TEST_ASSERT_NOT_NULL(g_units_invalid_sim.repl_state); + + captured = capture_stdout_around(run_set_units_bogus); + TEST_ASSERT_NOT_NULL_MESSAGE( + strstr(captured, "set units"), + "an unrecognized `set units` value must be rejected with a " + "units-specific usage message -- matching `set speed`'s " + "\"[sim] set speed: usage: set speed \" and `set verify`'s " + "\"[sim] set verify: usage: set verify on|off\" per-key " + "usage-error pattern (repl_params.c), NOT the generic \"[sim] set: " + "unrecognized parameter 'units'\" message that fires today for an " + "unrecognized KEY (units IS a recognized key once task 7.2 lands; " + "only its VALUE here is invalid)"); + TEST_ASSERT_EQUAL_MESSAGE( + XCE_REPL_UNITS_BOTH, g_units_invalid_sim.repl_state->units_mode, + "a rejected `set units` value must leave the current display mode " + "unchanged -- same discipline `set speed`/`set verify`'s own " + "malformed-input tests already establish for their own fields"); + free(captured); + + printf("test_set_units_invalid_value_rejected_like_existing_pattern: " + "PASS\n"); +} + /* ---------------------------------------------------------------------- */ /* Test: `info config` reports speed/output/verify/events_processed/ */ /* current_ts/sessions, both while output is off and while it is active */ @@ -863,6 +994,9 @@ int main(void) RUN_TEST(test_set_speed_updates_shadow_and_core_atomics); RUN_TEST(test_set_verify_toggles_flag); RUN_TEST(test_set_output_opens_writes_preamble_and_off_closes); + RUN_TEST(test_set_units_raw_human_both_update_display_mode); + RUN_TEST(test_set_units_defaults_to_both_without_explicit_command); + RUN_TEST(test_set_units_invalid_value_rejected_like_existing_pattern); RUN_TEST(test_info_config_reports_current_parameters); RUN_TEST(test_help_lists_snapshot_and_set_commands);