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
|
|
@ -974,6 +974,23 @@ sched_repl_equivalence_test = executable(
|
|||
dependencies: [scheduler_test_deps, libedit_dep],
|
||||
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
|
||||
# (component-failover-adoption, task 3.1, boundary: SchedulerControllerClient).
|
||||
# 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',
|
||||
sched_repl_equivalence_test, suite: 'scheduler',
|
||||
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',
|
||||
sched_scheduler_connect_test, suite: 'scheduler',
|
||||
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