swactor/apps/old-pipeline-parallel-inference/tests/t_role.rs

105 lines
3.4 KiB
Rust
Raw Normal View History

2026-05-20 07:41:30 +00:00
//! T-role: pure role-classification tests (TEST_SPEC §3).
//!
//! Surface: `StageRole::for_stage(stage, num_stages)`. The function is a
//! pure value-level classifier: no runtime, no env, no actor needed. These
//! tests are the cheapest possible guard against regressions in the
//! first/middle/last partition that the rest of the N-stage code relies on.
//!
//! The N range here (2..=16) is intentionally wider than the test matrix
//! we actually ship (N up to 5 today): if a future N raises `num_stages`,
//! it should not be the role-computation that breaks.
use pipeline_parallel_inference::stage_actor::StageRole;
/// Stage 0 is always `First`, for every supported `N`. The first stage's
/// identity does not depend on chain length.
#[test]
fn stage_zero_is_first_for_any_num_stages() {
for n in 2..=8 {
assert_eq!(
StageRole::for_stage(0, n),
StageRole::First,
"stage 0 of {n} stages should be First",
);
}
}
/// The terminal stage (`stage == N - 1`) is always `Last`, for every
/// supported `N`. The last stage's identity does not depend on chain length.
#[test]
fn last_index_is_last_for_any_num_stages() {
for n in 2..=8 {
assert_eq!(
StageRole::for_stage(n - 1, n),
StageRole::Last,
"stage {} of {n} stages should be Last",
n - 1,
);
}
}
/// Every interior index in a chain of 3+ stages is `Middle`. This is the
/// "if it's not first and not last, it's middle" invariant the actor
/// dispatch table relies on.
#[test]
fn middle_indices_are_middle() {
for n in 3..=8 {
for i in 1..(n - 1) {
assert_eq!(
StageRole::for_stage(i, n),
StageRole::Middle,
"stage {i} of {n} stages should be Middle",
);
}
}
}
/// A 2-stage chain has no middle stage at all: both valid inputs yield
/// `First` or `Last`. Nothing in the input space classifies as `Middle`
/// when `N == 2`.
#[test]
fn n_stages_two_has_no_middle() {
assert_eq!(StageRole::for_stage(0, 2), StageRole::First);
assert_eq!(StageRole::for_stage(1, 2), StageRole::Last);
// Exhaustive over the valid input space at N=2 — no Middle appears.
for stage in 0..2 {
assert_ne!(
StageRole::for_stage(stage, 2),
StageRole::Middle,
"N=2 stage {stage} must not be Middle",
);
}
}
/// For every `N ∈ {2..=16}`, exactly one stage is `First`, exactly one is
/// `Last`, and the remaining `N - 2` stages are `Middle`. Cross-checks the
/// three role predicates against each other as a partition.
#[test]
fn role_partition_property() {
for n in 2..=16u32 {
let mut first = 0u32;
let mut middle = 0u32;
let mut last = 0u32;
for stage in 0..n {
match StageRole::for_stage(stage, n) {
StageRole::First => first += 1,
StageRole::Middle => middle += 1,
StageRole::Last => last += 1,
}
}
assert_eq!(first, 1, "N={n}: expected exactly one First, got {first}");
assert_eq!(last, 1, "N={n}: expected exactly one Last, got {last}");
assert_eq!(
middle,
n - 2,
"N={n}: expected {} Middle, got {middle}",
n - 2,
);
assert_eq!(
first + middle + last,
n,
"N={n}: role counts must partition the full stage set",
);
}
}