105 lines
3.4 KiB
Rust
105 lines
3.4 KiB
Rust
|
|
//! 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",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|