feat(scheduler-sim-repl-verbosity): task 1.1 - RED-phase tests for job identifier range compression
Adds failing unit tests for repl_fmt_jobid_range() covering contiguous ranges, non-contiguous sub-ranges, multiple array bases, mixed array/ non-array ids, and a too-small output buffer (Requirement 4, AC 4.1-4.5). The function itself is not yet implemented (task 1.2) - build fails at link time with undefined reference to repl_fmt_jobid_range, confirming the RED phase. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YAWMhfRJFnmmnG8h97yUDd
This commit is contained in:
parent
00fc0a43e0
commit
f165e3a751
3 changed files with 401 additions and 12 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
# Implementation Plan
|
# Implementation Plan
|
||||||
|
|
||||||
- [ ] 1. Foundation: job identifier range compression
|
- [ ] 1. Foundation: job identifier range compression
|
||||||
- [ ] 1.1 Write failing unit tests for compressing job-array identifier listings into index ranges
|
- [x] 1.1 Write failing unit tests for compressing job-array identifier listings into index ranges
|
||||||
- Cover a single contiguous range, non-contiguous indices within one array submission, multiple distinct array submissions in one listing, a mix of array and non-array identifiers, and an output buffer too small to hold the full listing
|
- Cover a single contiguous range, non-contiguous indices within one array submission, multiple distinct array submissions in one listing, a mix of array and non-array identifiers, and an output buffer too small to hold the full listing
|
||||||
- Observable: the new test cases execute and fail against the current codebase, which has no compression behavior yet
|
- Observable: the new test cases execute and fail against the current codebase, which has no compression behavior yet
|
||||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
|
|
||||||
- [ ] 2. Core: per-round decision summary, repetitive-round collapsing, and the shared range-flush capability
|
- [ ] 2. Core: per-round decision summary, repetitive-round collapsing, and the shared range-flush capability
|
||||||
- [ ] 2.1 Write failing tests for round-level decision summaries, collapsing, and the shared flush capability
|
- [ ] 2.1 Write failing tests for round-level decision summaries, collapsing, and the shared flush capability
|
||||||
- Cover: a round with at least one decision produces one summary line naming decision counts and affected job identifiers; a round with zero decisions produces no individual line; two or more consecutive idle rounds collapse into a single range notification; two or more consecutive rounds with an identical decision outcome collapse into a single range notification; a collapsed range notification states its round bounds, round count, and covered time span; a change in outcome ends an active collapsed range; a job's status change between rounds is reflected in the following round's summary even without a dedicated dispatch event to observe; the shared flush capability safely does nothing when no range is active and is safe to call more than once in a row
|
- Cover: a round with at least one decision produces one summary line naming decision counts and affected job identifiers; a round with zero decisions produces no individual line; two or more consecutive idle rounds collapse into a single range notification; two or more consecutive rounds with an identical decision outcome collapse into a single range notification; a collapsed range notification states its round bounds, round count, and covered time span; a change in outcome ends an active collapsed range; a job's status change between rounds is reflected in the following round's summary even without a dedicated dispatch event to observe; the shared flush capability safely does nothing when no range is active and is safe to call more than once in a row; an active collapsed range open at replay completion is flushed and printed before `xce_repl_on_replay_complete()`'s existing completion banner
|
||||||
- Observable: the new test cases fail against today's unconditional per-decision echo, which has no summarizing, collapsing, or flush behavior
|
- Observable: the new test cases fail against today's unconditional per-decision echo, which has no summarizing, collapsing, or flush behavior
|
||||||
- _Requirements: 2.1, 2.2, 2.4, 3.1, 3.2, 3.3, 3.4_
|
- _Requirements: 2.1, 2.2, 2.4, 3.1, 3.2, 3.3, 3.4, 3.6_
|
||||||
- _Boundary: Round Rollup_
|
- _Boundary: Round Rollup_
|
||||||
- _Depends: 1.2_
|
- _Depends: 1.2_
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
- [ ] 3. (P) Core: mode-aware event echo, including ending an active collapsed range on structural events or breakpoint hits — Depends: 2.2
|
- [ ] 3. (P) Core: mode-aware event echo, including ending an active collapsed range on structural events or breakpoint hits — Depends: 2.2
|
||||||
- [ ] 3.1 (P) Write failing tests for suppressing routine event chatter and for ending an active collapsed range at the right moments
|
- [ ] 3.1 (P) Write failing tests for suppressing routine event chatter and for ending an active collapsed range at the right moments
|
||||||
- Cover: a routine per-tick event (job progress, resource usage, or resource value update) produces no line under continuous execution; the same event still produces a line while single-stepping; a structural change (new jobs, workers, resources, or configuration arriving) still produces a line under continuous execution and ends any active collapsed range first; an event matching a breakpoint still interrupts continuous execution regardless of its classification, and ends any active collapsed range before the interruption is shown
|
- Cover: a routine per-tick event (a job status change, job progress update, job resource-usage update, queue update, or partial/delta resource-value update) produces no line under continuous execution; the same event still produces a line while single-stepping; a structural change (new jobs, workers, or resources arriving, a full resource-value snapshot arriving, or configuration pushed) still produces a line under continuous execution and ends any active collapsed range first; an event matching a breakpoint still interrupts continuous execution regardless of its classification, and ends any active collapsed range before the interruption is shown
|
||||||
- Observable: the new test cases fail against today's unconditional per-event echo, which has no classification or range-ending behavior
|
- Observable: the new test cases fail against today's unconditional per-event echo, which has no classification or range-ending behavior
|
||||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 3.5, 3.6_
|
- _Requirements: 1.1, 1.2, 1.3, 1.4, 3.5, 3.6_
|
||||||
- _Boundary: Event Echo Gate_
|
- _Boundary: Event Echo Gate_
|
||||||
|
|
@ -58,12 +58,64 @@
|
||||||
- _Requirements: 2.3, 5.1, 5.4_
|
- _Requirements: 2.3, 5.1, 5.4_
|
||||||
- _Boundary: sim_print_decision gate_
|
- _Boundary: sim_print_decision gate_
|
||||||
|
|
||||||
- [ ] 5. Validation: regression and real-data verification — Depends: 3.2, 4.2
|
- [ ] 6. (P) REPL prompt shows current session and timestamp
|
||||||
- [ ] 5.1 Verify single-stepping and every other existing REPL command remain unchanged
|
- [ ] 6.1 (P) Write failing tests for the dynamic REPL prompt
|
||||||
- Confirm single-stepping output, and the inspection/breakpoint/parameter/snapshot commands' behavior and output, match their pre-feature baseline exactly; confirm no new command was introduced
|
- Cover: the prompt is the plain `(sim) ` before any session has begun; once at least one session has run, the prompt includes the current session count and a human-readable timestamp; this holds in both `step` and `continue` modes; the prompt never alters any command's own output
|
||||||
- Observable: the existing REPL test suite's pre-existing assertions all still pass unmodified, and a step-mode transcript comparison shows no textual difference from the pre-feature baseline
|
- Observable: the new test cases fail against today's static `XCE_REPL_PROMPT` macro, which never varies
|
||||||
- _Requirements: 5.1, 5.2, 5.3_
|
- _Requirements: 5.5_
|
||||||
|
- _Boundary: REPL Prompt Context_
|
||||||
|
|
||||||
- [ ] 5.2 Run the full scheduler test suite and re-verify against the real replay capture that originally motivated this feature
|
- [ ] 6.2 (P) Implement the dynamic prompt
|
||||||
- Observable: the full test suite passes with no regressions, and re-running the interactive REPL's continuous execution against the real capture reproduces the collapsed output shape validated during design, cutting a several-thousand-line transcript down to a handful of lines while preserving every state change
|
- Build the prompt fresh on every `repl_command_loop()` iteration from the current session count and `xce_core_now()`/`repl_format_ts_human()`, falling back to the original plain prompt before any session begins
|
||||||
- _Requirements: 2.1, 2.2, 3.1, 3.2, 3.3, 3.4_
|
- Observable: all tests from 6.1 pass; a live `step`/`continue` session shows the session/timestamp in the prompt starting from the first completed session, with no change to any command's own printed output
|
||||||
|
- _Requirements: 5.5_
|
||||||
|
- _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
|
||||||
|
- 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_
|
||||||
|
- _Boundary: Resource Value Formatter_
|
||||||
|
|
||||||
|
- [ ] 7.2 (P) Implement the classification/scaling helpers, the `units_mode` setting, and `set units` parsing
|
||||||
|
- Add the bytes-valued classifier and the adaptive human-scaling formatter as pure functions; add the `units_mode` field to the REPL session state, explicitly defaulted to `both` (not left to zero-initialization); wire `set units raw|human|both` into the existing `set` command dispatch alongside `speed`/`output`/`verify`
|
||||||
|
- Observable: all tests from 7.1 pass; the helpers and the new setting are ready for the resource-listing rendering work to adopt
|
||||||
|
- _Requirements: 6.1, 6.2, 6.3_
|
||||||
|
- _Boundary: Resource Value Formatter_
|
||||||
|
|
||||||
|
- [ ] 8. (P) `info worker`/`info resource` grouped table layout and units-mode-aware rendering — Depends: 1.2, 7.2
|
||||||
|
- [ ] 8.1 (P) Write failing tests for the resource-listing table layout and units-mode rendering
|
||||||
|
- Cover: `info worker`'s and `info resource`'s resource listings render as an aligned table grouped by category (consumable/capacity, load metrics, static hardware attributes, where applicable to that listing) instead of one flat list; a whole-number unitless value renders with no trailing `.00`, a fractional one keeps its decimals; each `units_mode` (`raw`/`human`/`both`) selects the corresponding rendering for a byte/megabyte-valued resource; the underlying stored/wire value and replay determinism are unaffected; `info worker`'s assigned-job listing renders using the existing jobid-range-compression helper
|
||||||
|
- Observable: the new test cases fail against today's unconditional flat per-resource `fprintf` loop and un-compressed assigned-job listing
|
||||||
|
- _Requirements: 6.4, 6.5, 6.6, 4.5_
|
||||||
|
- _Boundary: Resource Value Formatter, Jobid Range Compressor_
|
||||||
|
|
||||||
|
- [ ] 8.2 (P) Implement the grouped table rendering for `info worker` and `info resource`
|
||||||
|
- Replace the flat per-resource print loop with a shared, grouped, aligned-column table renderer reading `rs->units_mode`; adopt the jobid-range-compression helper for the assigned-job listing
|
||||||
|
- Observable: all tests from 8.1 pass; a live `info worker all` against a real captured worker reproduces the hand-validated grouped-table shape instead of 20 unstructured lines
|
||||||
|
- _Requirements: 6.4, 6.5, 6.6, 4.5_
|
||||||
|
- _Boundary: Resource Value Formatter, Jobid Range Compressor_
|
||||||
|
|
||||||
|
- [ ] 9. (P) Discoverable `help` listing and `break` event types — Depends: 7.2
|
||||||
|
- [ ] 9.1 (P) Write failing tests for the reorganized `help` listing and `break`'s event-type discoverability
|
||||||
|
- Cover: `help`'s output groups commands into the four labeled sections (execution control, breakpoints, state inspection, snapshot and parameters), uses one consistent column width throughout, lists `info config` alongside the other `info` subcommands, and lists `set units` alongside `set speed`/`set output`/`set verify` in the snapshot-and-parameters section; `help`'s `break` line and `break`'s unrecognized-event-type error both list the complete, current set of valid event types from the same source; no command's existence, argument parsing, or other output changes
|
||||||
|
- Observable: the new test cases fail against today's unordered, hand-padded `help` listing (with `info config` disconnected from its siblings) and `break`'s error message, which names no valid alternatives
|
||||||
|
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_
|
||||||
|
- _Boundary: Help & Breakpoint Discoverability_
|
||||||
|
|
||||||
|
- [ ] 9.2 (P) Implement the shared event-type accessor, the reorganized `help`, and `break`'s enriched error message
|
||||||
|
- Add one shared accessor rendering `BP_KIND_TABLE`'s complete token set; rewrite `repl_print_help()` into the four labeled, consistently-aligned sections with `info config` moved next to its siblings, `set units` listed alongside `set speed`/`set output`/`set verify`, and the accessor's output appended to the `break` line; call the same accessor from `repl_cmd_break()`'s unrecognized-event-type error
|
||||||
|
- Observable: all tests from 9.1 pass; `help` and `break`'s error message can never disagree with each other or with `BP_KIND_TABLE`, since both read the same accessor
|
||||||
|
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6_
|
||||||
|
- _Boundary: Help & Breakpoint Discoverability_
|
||||||
|
|
||||||
|
- [ ] 10. Validation: regression and real-data verification — Depends: 3.2, 4.2, 6.2, 8.2, 9.2
|
||||||
|
- [ ] 10.1 Verify single-stepping and every other existing REPL command remain unchanged outside this feature's explicit exceptions
|
||||||
|
- Confirm single-stepping's per-event/per-decision output, and the inspection/breakpoint/parameter/snapshot commands' behavior and output, match their pre-feature baseline exactly except for the prompt content (Requirement 5.5), `info`'s job-identifier/resource-value listings (Requirements 4.5, 6), and `help`/`break`'s discoverability changes (Requirement 7); confirm no new top-level command was introduced
|
||||||
|
- Observable: the existing REPL test suite's pre-existing assertions all still pass unmodified, and a step-mode transcript comparison shows no textual difference from the pre-feature baseline outside the prompt line itself
|
||||||
|
- _Requirements: 5.1, 5.2, 5.3, 5.4_
|
||||||
|
|
||||||
|
- [ ] 10.2 Run the full scheduler test suite and re-verify against the real replay capture that originally motivated this feature
|
||||||
|
- Observable: the full test suite passes with no regressions, and re-running the interactive REPL's continuous execution against the real capture reproduces the collapsed output shape validated during design, cutting a several-thousand-line transcript down to a handful of lines while preserving every state change; a `step`/`info worker`/`help`/`break` pass over the same capture shows the new prompt content, grouped resource table, reorganized help listing, and enriched break error, with no change to any scheduling decision
|
||||||
|
- _Requirements: 2.1, 2.2, 3.1, 3.2, 3.3, 3.4, 5.5, 6.6, 7.1, 7.2, 7.3, 7.4, 7.5_
|
||||||
|
|
|
||||||
|
|
@ -974,6 +974,23 @@ sched_repl_equivalence_test = executable(
|
||||||
dependencies: [scheduler_test_deps, libedit_dep],
|
dependencies: [scheduler_test_deps, libedit_dep],
|
||||||
install: false)
|
install: false)
|
||||||
|
|
||||||
|
# RED-phase contract test for the REPL's job-identifier range compressor
|
||||||
|
# (scheduler-sim-repl-verbosity spec, task 1.1, Requirement 4). Unlike every
|
||||||
|
# other sched_repl_*_test above, this is NOT a white-box #include of
|
||||||
|
# ../src/repl.c and friends -- repl_fmt_jobid_range() is a pure formatter
|
||||||
|
# with no xce_sched_sim_driver_t/xce_sched_core_t dependency, so this file
|
||||||
|
# declares it as a plain extern instead (see test_repl_output_compression.c's
|
||||||
|
# own header comment for the full rationale). As of task 1.1, the function
|
||||||
|
# has no definition anywhere in this codebase (task 1.2 adds it), so this
|
||||||
|
# target is EXPECTED to fail at link time -- that is task 1.1's own
|
||||||
|
# Observable evidence, not a defect in this build file.
|
||||||
|
sched_repl_output_compression_test = executable(
|
||||||
|
'sched_repl_output_compression_test',
|
||||||
|
['test_repl_output_compression.c'],
|
||||||
|
include_directories:[incdir],
|
||||||
|
dependencies: [scheduler_test_deps],
|
||||||
|
install: false)
|
||||||
|
|
||||||
# Tests for the scheduler's unified candidate-list connect/rejoin pipeline
|
# Tests for the scheduler's unified candidate-list connect/rejoin pipeline
|
||||||
# (component-failover-adoption, task 3.1, boundary: SchedulerControllerClient).
|
# (component-failover-adoption, task 3.1, boundary: SchedulerControllerClient).
|
||||||
# Unlike the white-box #include-the-.c-file tests above, this target links
|
# Unlike the white-box #include-the-.c-file tests above, this target links
|
||||||
|
|
@ -1324,6 +1341,9 @@ test('scheduler repl snapshot params test',
|
||||||
test('scheduler repl equivalence test',
|
test('scheduler repl equivalence test',
|
||||||
sched_repl_equivalence_test, suite: 'scheduler',
|
sched_repl_equivalence_test, suite: 'scheduler',
|
||||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||||
|
test('scheduler repl output compression test',
|
||||||
|
sched_repl_output_compression_test, suite: 'scheduler',
|
||||||
|
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||||
test('scheduler connect pipeline test',
|
test('scheduler connect pipeline test',
|
||||||
sched_scheduler_connect_test, suite: 'scheduler',
|
sched_scheduler_connect_test, suite: 'scheduler',
|
||||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||||
|
|
|
||||||
317
src/scheduler/testsuite/test_repl_output_compression.c
Normal file
317
src/scheduler/testsuite/test_repl_output_compression.c
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
/*
|
||||||
|
* test_repl_output_compression.c - RED-phase contract test for the REPL's
|
||||||
|
* job-identifier range compressor (scheduler-sim-repl-verbosity spec, task
|
||||||
|
* 1.1, Requirement 4 "Job Identifier Range Compression", ACs 4.1-4.5).
|
||||||
|
*
|
||||||
|
* THIS TASK (1.1) IS RED-PHASE ONLY: `repl_fmt_jobid_range()` does not exist
|
||||||
|
* anywhere in this codebase yet -- task 1.2 (a separate, later task)
|
||||||
|
* implements it. Building this file is therefore expected to FAIL AT LINK
|
||||||
|
* TIME (undefined reference to `repl_fmt_jobid_range`), not to fail a
|
||||||
|
* runtime assertion -- that link failure IS this task's Observable evidence
|
||||||
|
* per tasks.md. Once task 1.2 lands a real definition, this same file
|
||||||
|
* (unmodified) becomes the GREEN-phase proof.
|
||||||
|
*
|
||||||
|
* Declaring the function under test: every existing test_repl_*.c file in
|
||||||
|
* this directory white-box #includes ../src/repl.c (plus repl_break.c/
|
||||||
|
* repl_info.c/repl_params.c/core.c/sim.c/...) to reach the REPL functions it
|
||||||
|
* exercises, because those functions need a real xce_sched_sim_driver_t/
|
||||||
|
* xce_sched_core_t fixture. repl_fmt_jobid_range() does not: per design.md's
|
||||||
|
* "Jobid Range Compressor" component, it is a pure formatter over a raw
|
||||||
|
* uint64_t jobid array with no dependency on sim/core/repl state. Pulling in
|
||||||
|
* repl.c wholesale here would ALSO drag in repl_internal.h's other extern
|
||||||
|
* cross-references (repl_cmd_break/repl_cmd_info/repl_cmd_set/...,
|
||||||
|
* implemented in sibling files this test does not include), producing a
|
||||||
|
* pile of unrelated undefined-reference errors and burying the one this
|
||||||
|
* task actually wants to prove. So this file declares the function locally
|
||||||
|
* as a plain `extern` instead -- once task 1.2 gives it a shared
|
||||||
|
* declaration in repl_internal.h (needed at that point because Round Rollup
|
||||||
|
* and repl_info.c's `info worker` will call it from other files), this
|
||||||
|
* local prototype becomes redundant but harmless (identical signature).
|
||||||
|
*
|
||||||
|
* Jobid fixtures: real, correctly bit-packed jobids built via this
|
||||||
|
* codebase's own MAKE_JOBID/TAG_JOB_ARRAY/SEQUENCE_HIGH_SHIFT macros
|
||||||
|
* (src/include/config.h, src/include/utils.h) -- the SAME encoding
|
||||||
|
* fmt_jobid_ex() (src/lib/utils.c) decodes: a 64-bit jobid packs
|
||||||
|
* (cluster:6 | apiserver:4 | timestamp:32 | tag:2 | sequence:20); for an
|
||||||
|
* array-tagged jobid (tag=TAG_JOB_ARRAY), the 20-bit sequence field splits
|
||||||
|
* further into a 5-bit "array base" (bits 15-19, EXTRACT_ARRAY_JOB) and a
|
||||||
|
* 15-bit "array index" (bits 0-14, EXTRACT_ARRAY_IDX) -- mirrored here by
|
||||||
|
* mk_array_jobid()'s `(array_base << SEQUENCE_HIGH_SHIFT) | idx` construction
|
||||||
|
* (the same shape idgen.c's own idgen() builds for a real array submission).
|
||||||
|
* A non-array jobid (tag=0) uses the full 20-bit sequence field as one plain
|
||||||
|
* value (mk_plain_jobid()).
|
||||||
|
*
|
||||||
|
* Assumed output contract (design.md's Service Interface documents the
|
||||||
|
* grouping/compression algorithm and postconditions, but not a byte-exact
|
||||||
|
* example -- this header comment fixes the literal format these tests pin,
|
||||||
|
* for task 1.2 to satisfy):
|
||||||
|
* - Groups (one per distinct (cluster,apiserver,timestamp,array-base)
|
||||||
|
* array submission, or one per standalone non-array jobid) are
|
||||||
|
* comma-joined ("," bare, no space -- matching this file family's own
|
||||||
|
* existing comma-list convention: repl_break.c's JOB_PUSH jobid listing
|
||||||
|
* and info-break filter listing, repl_info.c's repl_resource_flags_str()),
|
||||||
|
* in the input order each group is FIRST seen (design.md's own
|
||||||
|
* "distinct array bases appear in input order" postcondition).
|
||||||
|
* - A non-array jobid renders as "<cluster>-<apiserver>-<timestamp>-<seq>"
|
||||||
|
* -- fmt_jobid_ex()'s own non-array format, verbatim.
|
||||||
|
* - An array submission's compressed group renders as
|
||||||
|
* "<cluster>-<apiserver>-<timestamp>-<arraybase>[<run1>,<run2>,...]",
|
||||||
|
* each run being "<lo>-<hi>" for a contiguous run of >= 2 indices or a
|
||||||
|
* bare "<idx>" for a run of exactly 1, runs ascending and comma-joined
|
||||||
|
* inside the brackets (design.md: "compresses contiguous runs into
|
||||||
|
* [lo-hi] form, comma-joining non-contiguous runs WITHIN ONE BASE").
|
||||||
|
* - Buffer-too-small (Error Handling section): returns a negative value,
|
||||||
|
* and writes NOTHING into the caller's buf -- mirroring fmt_jobid_ex()'s
|
||||||
|
* own established pattern in this exact codebase (src/lib/utils.c):
|
||||||
|
* build the full rendering into a local scratch buffer first, and only
|
||||||
|
* ever touch the caller's buf if the complete result is proven to fit;
|
||||||
|
* "never truncates silently" (design.md's Error Handling section) rules
|
||||||
|
* out writing any partial/truncated prefix into the caller's buffer.
|
||||||
|
*/
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <unity/unity.h>
|
||||||
|
|
||||||
|
#include "utils.h" /* MAKE_JOBID, TAG_JOB_ARRAY, SEQUENCE_HIGH_SHIFT (via config.h) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* repl_fmt_jobid_range (task 1.2, NOT YET IMPLEMENTED -- see this file's own
|
||||||
|
* header comment above for why this is a local extern rather than a
|
||||||
|
* white-box #include). Signature copied verbatim from design.md's "Jobid
|
||||||
|
* Range Compressor" Service Interface.
|
||||||
|
*/
|
||||||
|
extern int repl_fmt_jobid_range(char* buf, size_t bufsz,
|
||||||
|
const uint64_t* jobids, int njobids);
|
||||||
|
|
||||||
|
void setUp(void) { return; }
|
||||||
|
void tearDown(void) { return; }
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Fixture helpers -- real, correctly bit-packed jobids (see header */
|
||||||
|
/* comment above for the exact encoding each mirrors). */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
static uint64_t mk_array_jobid(uint32_t cluster, uint32_t apiserver,
|
||||||
|
uint32_t timestamp, uint32_t array_base,
|
||||||
|
uint32_t idx)
|
||||||
|
{
|
||||||
|
uint64_t seq = (((uint64_t)array_base) << SEQUENCE_HIGH_SHIFT)
|
||||||
|
| (uint64_t)idx;
|
||||||
|
|
||||||
|
return MAKE_JOBID(cluster, apiserver, timestamp, TAG_JOB_ARRAY, seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t mk_plain_jobid(uint32_t cluster, uint32_t apiserver,
|
||||||
|
uint32_t timestamp, uint32_t seq)
|
||||||
|
{
|
||||||
|
return MAKE_JOBID(cluster, apiserver, timestamp, 0x0, (uint64_t)seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Test 1 (AC 4.1): a single contiguous range from one array submission. */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Three array jobids sharing one submission (cluster=3, apiserver=2,
|
||||||
|
* ts=1700000000, array base=1 -> job field 1<<15=32768), indices 0,1,2
|
||||||
|
* contiguous, must compress into a single "[0-2]" range on that one base
|
||||||
|
* prefix -- not three separate entries.
|
||||||
|
*/
|
||||||
|
static void test_contiguous_array_range_compresses_to_single_range(void)
|
||||||
|
{
|
||||||
|
uint64_t jobids[3];
|
||||||
|
char buf[256];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
jobids[0] = mk_array_jobid(3, 2, 1700000000, 1, 0);
|
||||||
|
jobids[1] = mk_array_jobid(3, 2, 1700000000, 1, 1);
|
||||||
|
jobids[2] = mk_array_jobid(3, 2, 1700000000, 1, 2);
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
rc = repl_fmt_jobid_range(buf, sizeof(buf), jobids, 3);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_STRING("3-2-1700000000-32768[0-2]", buf);
|
||||||
|
TEST_ASSERT_EQUAL_INT((int)strlen("3-2-1700000000-32768[0-2]"), rc);
|
||||||
|
|
||||||
|
printf("test_contiguous_array_range_compresses_to_single_range: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Test 2 (AC 4.2): non-contiguous indices within one array submission. */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One array submission (same base as Test 1) with indices 0,1,2,5,6,7,10 --
|
||||||
|
* three contiguous sub-runs ([0-2], [5-7]) plus one lone index (10) -- must
|
||||||
|
* render as comma-joined sub-ranges INSIDE that one base's brackets, not as
|
||||||
|
* three/four separately-prefixed base entries.
|
||||||
|
*/
|
||||||
|
static void test_noncontiguous_indices_compress_to_commajoined_subranges(
|
||||||
|
void)
|
||||||
|
{
|
||||||
|
uint64_t jobids[7];
|
||||||
|
char buf[256];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
jobids[0] = mk_array_jobid(3, 2, 1700000000, 1, 0);
|
||||||
|
jobids[1] = mk_array_jobid(3, 2, 1700000000, 1, 1);
|
||||||
|
jobids[2] = mk_array_jobid(3, 2, 1700000000, 1, 2);
|
||||||
|
jobids[3] = mk_array_jobid(3, 2, 1700000000, 1, 5);
|
||||||
|
jobids[4] = mk_array_jobid(3, 2, 1700000000, 1, 6);
|
||||||
|
jobids[5] = mk_array_jobid(3, 2, 1700000000, 1, 7);
|
||||||
|
jobids[6] = mk_array_jobid(3, 2, 1700000000, 1, 10);
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
rc = repl_fmt_jobid_range(buf, sizeof(buf), jobids, 7);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_STRING("3-2-1700000000-32768[0-2,5-7,10]", buf);
|
||||||
|
TEST_ASSERT_EQUAL_INT(
|
||||||
|
(int)strlen("3-2-1700000000-32768[0-2,5-7,10]"), rc);
|
||||||
|
|
||||||
|
printf("test_noncontiguous_indices_compress_to_commajoined_subranges: "
|
||||||
|
"PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Test 3 (AC 4.3): more than one distinct array submission in one listing.*/
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Two distinct array submissions -- base A (ts=1700000000) and base B
|
||||||
|
* (ts=1700000500, deliberately sharing the SAME numeric array-base value 1
|
||||||
|
* as base A, so this only passes if grouping honors the full
|
||||||
|
* (cluster,apiserver,timestamp,base) key, not just the array-base number)
|
||||||
|
* -- interleaved in the input array (A0,B0,A1,B1,A2) to prove grouping does
|
||||||
|
* not depend on adjacency. Each submission's indices must compress
|
||||||
|
* independently, and the two groups must appear in first-seen order (A
|
||||||
|
* first, since jobids[0] is A's first index).
|
||||||
|
*/
|
||||||
|
static void test_multiple_array_submissions_compress_independently(void)
|
||||||
|
{
|
||||||
|
uint64_t jobids[5];
|
||||||
|
char buf[256];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
jobids[0] = mk_array_jobid(3, 2, 1700000000, 1, 0); /* A0 */
|
||||||
|
jobids[1] = mk_array_jobid(3, 2, 1700000500, 1, 0); /* B0 */
|
||||||
|
jobids[2] = mk_array_jobid(3, 2, 1700000000, 1, 1); /* A1 */
|
||||||
|
jobids[3] = mk_array_jobid(3, 2, 1700000500, 1, 1); /* B1 */
|
||||||
|
jobids[4] = mk_array_jobid(3, 2, 1700000000, 1, 2); /* A2 */
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
rc = repl_fmt_jobid_range(buf, sizeof(buf), jobids, 5);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_STRING(
|
||||||
|
"3-2-1700000000-32768[0-2],3-2-1700000500-32768[0-1]", buf);
|
||||||
|
TEST_ASSERT_EQUAL_INT(
|
||||||
|
(int)strlen(
|
||||||
|
"3-2-1700000000-32768[0-2],3-2-1700000500-32768[0-1]"),
|
||||||
|
rc);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"test_multiple_array_submissions_compress_independently: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Test 4 (AC 4.4): a listing mixing array and non-array job identifiers. */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A listing interleaving two standalone (non-array) jobids around one
|
||||||
|
* array submission's indices: plain1, arr0, arr1, plain2, arr2. The two
|
||||||
|
* plain ids must each render individually at their own first-seen listing
|
||||||
|
* position, while the array indices -- though non-adjacent in the input --
|
||||||
|
* still collapse into one compressed "[0-2]" group at ITS first-seen
|
||||||
|
* position (right after plain1).
|
||||||
|
*/
|
||||||
|
static void test_mixed_array_and_nonarray_ids_shows_nonarray_individually(
|
||||||
|
void)
|
||||||
|
{
|
||||||
|
uint64_t jobids[5];
|
||||||
|
char buf[256];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
jobids[0] = mk_plain_jobid(3, 2, 1700000000, 10); /* plain1 */
|
||||||
|
jobids[1] = mk_array_jobid(3, 2, 1700000000, 1, 0); /* arr0 */
|
||||||
|
jobids[2] = mk_array_jobid(3, 2, 1700000000, 1, 1); /* arr1 */
|
||||||
|
jobids[3] = mk_plain_jobid(3, 2, 1700000000, 20); /* plain2 */
|
||||||
|
jobids[4] = mk_array_jobid(3, 2, 1700000000, 1, 2); /* arr2 */
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
rc = repl_fmt_jobid_range(buf, sizeof(buf), jobids, 5);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_STRING(
|
||||||
|
"3-2-1700000000-10,3-2-1700000000-32768[0-2],3-2-1700000000-20",
|
||||||
|
buf);
|
||||||
|
TEST_ASSERT_EQUAL_INT(
|
||||||
|
(int)strlen("3-2-1700000000-10,3-2-1700000000-32768[0-2],"
|
||||||
|
"3-2-1700000000-20"),
|
||||||
|
rc);
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"test_mixed_array_and_nonarray_ids_shows_nonarray_individually: "
|
||||||
|
"PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
/* Test 5 (AC 4.5 / design.md Error Handling): buffer too small to hold */
|
||||||
|
/* the full listing. */
|
||||||
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The same three-jobid contiguous-range fixture as Test 1 (whose full
|
||||||
|
* rendering is 26 bytes, "3-2-1700000000-32768[0-2]" + NUL) passed with a
|
||||||
|
* caller bufsz of 4 -- far too small. Must fail safely: a negative return,
|
||||||
|
* and NOT ONE byte of the caller's buffer touched (see this file's header
|
||||||
|
* comment for why "never truncates silently" is read as "writes nothing on
|
||||||
|
* failure", mirroring fmt_jobid_ex()'s own local-scratch-buffer-first
|
||||||
|
* pattern in this codebase). buf is allocated larger (64 bytes) than the
|
||||||
|
* bufsz actually passed (4) specifically so a bug that ignores bufsz and
|
||||||
|
* writes into the full physical buffer is caught too, not just a bug that
|
||||||
|
* overflows past the physical allocation.
|
||||||
|
*/
|
||||||
|
static void test_buffer_too_small_fails_safely_without_truncating(void)
|
||||||
|
{
|
||||||
|
uint64_t jobids[3];
|
||||||
|
char buf[64];
|
||||||
|
char sentinel[64];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
jobids[0] = mk_array_jobid(3, 2, 1700000000, 1, 0);
|
||||||
|
jobids[1] = mk_array_jobid(3, 2, 1700000000, 1, 1);
|
||||||
|
jobids[2] = mk_array_jobid(3, 2, 1700000000, 1, 2);
|
||||||
|
|
||||||
|
memset(buf, 0xAA, sizeof(buf));
|
||||||
|
memset(sentinel, 0xAA, sizeof(sentinel));
|
||||||
|
|
||||||
|
rc = repl_fmt_jobid_range(buf, 4, jobids, 3);
|
||||||
|
|
||||||
|
TEST_ASSERT_TRUE_MESSAGE(rc < 0,
|
||||||
|
"a buffer too small for the full listing must "
|
||||||
|
"return a negative value, never a truncated "
|
||||||
|
"success");
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY_MESSAGE(
|
||||||
|
sentinel, buf, sizeof(buf),
|
||||||
|
"on failure, no byte of the caller's buffer may be written -- "
|
||||||
|
"silent truncation is explicitly disallowed (design.md Error "
|
||||||
|
"Handling)");
|
||||||
|
|
||||||
|
printf(
|
||||||
|
"test_buffer_too_small_fails_safely_without_truncating: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
UNITY_BEGIN();
|
||||||
|
|
||||||
|
RUN_TEST(test_contiguous_array_range_compresses_to_single_range);
|
||||||
|
RUN_TEST(test_noncontiguous_indices_compress_to_commajoined_subranges);
|
||||||
|
RUN_TEST(test_multiple_array_submissions_compress_independently);
|
||||||
|
RUN_TEST(test_mixed_array_and_nonarray_ids_shows_nonarray_individually);
|
||||||
|
RUN_TEST(test_buffer_too_small_fails_safely_without_truncating);
|
||||||
|
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue