test(scheduler-allocation): prove seqjob rewrap preserves host selection

Add a dedicated equivalence suite (test_seqjob_rewrap_equivalence.c)
covering multi-host fixtures the trivial single-host test doesn't reach:
select-filter-only rejection, resource-only rejection, a combined
select-then-resource-then-success ordering, and per-host debit
correctness. The iteration-order test is grounded in the deleted
pre-rewrap sched_collect_workers() code path (verified via git show
against the parent commit) and the unmodified xlist.c primitive, not
merely in host_iter.c's own traversal, so it proves equivalence to the
old behavior rather than internal self-consistency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYdtrEWWZfKRRGED7raEYg
This commit is contained in:
Rongsong Shen 2026-09-04 19:35:38 +00:00
parent b18c5f780d
commit 9d64ba0d69
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
3 changed files with 621 additions and 1 deletions

View file

@ -44,7 +44,7 @@
- Add a guard so seqjob's resource-request preparation only ever acts on single-task jobs, closing the fan-out gap this creates for the parallel allocator's later fix
- Observable: seqjob's allocation still runs end to end using the shared iterators, with no allocator-specific worker-collection logic left in seqjob's own code
- _Requirements: 9.1, 4.6_
- [ ] 3.2 Prove seqjob's rewrap preserves today's host-selection behavior
- [x] 3.2 Prove seqjob's rewrap preserves today's host-selection behavior
- Write a test suite constructing fixture states with multiple candidate hosts, including at least one host failing the select filter, asserting identical host selection to today's pre-rewrap behavior given identical worker/resource state
- Observable: a passing equivalence test suite demonstrates no behavior change across fixture states beyond the single-obvious-host trivial case
- _Requirements: 9.1_
@ -113,3 +113,5 @@
- (1.2) `resreq_fp`'s ingest-time population lives in `core_build_primary_schedobj()` (`src/scheduler/src/core_events.c`), right after `primary->resreq = resreq;` — confirmed wired (min_tasks/max_tasks/select_node_id/rusage_hash all set from the parsed `xce_primary_resreq_t`) as of task 1.2 round 2, regression-covered by `test_core_build_primary_schedobj_populates_resreq_fp_from_parsed_resreq` in `src/scheduler/testsuite/test_core_events.c`. Round 1 added the type/field/hash machinery but never wired this call site, leaving `resreq_fp` permanently zeroed — do not rediscover this gap in a later task.
- (3.1) The per-host try-alloc leaf (`sched_try_alloc_on_host_(xce_sched_worker_t* w, xce_scheduler_t* sched, xce_sched_ctx_t* ctx)` — select check → `sched_ctx_get_uncomit_host` → `plugin_prepare_allocation` → `sched_check_host_satisfy` → `xvec_idval(..., XVEC_ADD_FLOAT)`) now lives in `resource.c`, exported (non-static, non-variadic) and declared in `scheduler.h`. **Task 4.2 (parjob's per-bucket allocation loop) must call this exact shared function from its own bucket/host_iter loop, not write a second copy.** Round 1 of this task wrote a private ~70-line duplicate (`seqjob_try_alloc_on_host_`) because the original was `static`/`va_list`-based; round 2 fixed this by exporting the real function and deleting both the duplicate and the now-dead `sched_try_alloc_on_hosts()` variadic wrapper. This is the same reimplementation-vs-share risk task 1.4 hit in test code — here it recurred in production code, one task later.
- (3.1) `src/scheduler/meson.build`'s production `scheduler_srcs` list was missing `host_iter.c` since task 2.1 only wired that file into `testsuite/meson.build` — invisible until task 3.1 became the first *production* caller of `host_iter.c`'s functions (a shared .c file with zero production callers builds/links fine without being in `scheduler_srcs` at all, so task 2.1's own green build/test run couldn't have caught this). Any future task adding a new shared scheduler `.c` file should double check it lands in `scheduler_srcs`, not only `testsuite/meson.build`, even if nothing calls it yet.
- (3.2) `host_iter.c`'s pre-filter select check and `sched_try_alloc_on_host_()`'s (`resource.c`) own independent `sched_meet_host_selection` re-check are pre-existing, intentional defense-in-depth: both run the identical check against the same live state, so a regression in either one alone is invisible at the observable-pick level — only a regression in *both simultaneously* changes behavior. This is benign and predates this feature; task 4.2 (parjob) will inherit the same double-check when it reuses `host_iter.c`/`sched_try_alloc_on_host_()`, and should not "fix" the redundancy as part of that task.
- (3.2) `sched_collect_workers()`'s deleted (task 3.1) iteration and `host_iter.c`'s `iter_make()/iter_next()` iteration are structurally identical, not coincidentally similar: both called the exact same third-party `iter_make()/iter_next()` (RTTI lib) over the literal same `sched->core.workers` var, and the old `xlist_find()` walk was forward/insertion-order — confirmed via `git show b18c5f78^:src/scheduler/src/scheduler.c` and `xlist.c` (unmodified across this feature). Useful precedent if task 4.1/4.2's parjob equivalence claims ever need the same grounding.