test: constrain SectorSource speed forwarding and PassProgress percentages
Mutation testing left both unconstrained. sector/mod.rs — set_speed on the Box<dyn> and &mut dyn forwarding impls could be replaced with an empty body and nothing failed. This one hides better than the read methods because the trait's own default body is already a no-op, so a forwarder that swallowed the call is indistinguishable from a source with no speed control. Consequence is a silently absent value, not a wrong one: the recovery path lowers read speed through a damaged region, and a swallowed call leaves the drive at full speed while the caller believes it slowed down. Routed through a generic S: SectorSource bound, since a direct call on a &mut dyn receiver auto-derefs to the vtable and never enters the forwarding body. progress.rs — 42 survivors. All four percentage accessors could return a constant, read the wrong byte counter, or have their divide-by-zero guard inverted. Added exact-value tests (25%, not 'some percentage'), both sides of each guard, the overshoot clamp, and one test setting all three disc counters to distinct values at once — without it, a swapped field still passes every single-counter test. The Progress blanket impl for closures could return a constant true. That return value is the cancellation signal, so a constant-true body makes every closure-based consumer uncancellable. Each mutation applied, observed red, reverted.
This commit is contained in:
@@ -415,6 +415,13 @@ mod tests {
|
||||
s.read_sectors(lba, count, buf, recovery)
|
||||
}
|
||||
|
||||
/// Same, for the speed lever. The trait's own `set_speed` default is a
|
||||
/// no-op, so a forwarding body that also did nothing is indistinguishable
|
||||
/// from the default unless the call is routed through the generic bound.
|
||||
fn set_speed_generic<S: SectorSource>(mut s: S, kbs: u16) {
|
||||
s.set_speed(kbs);
|
||||
}
|
||||
|
||||
/// Same, for the FUA entry point.
|
||||
fn read_fua_generic<S: SectorSource>(
|
||||
mut s: S,
|
||||
@@ -496,4 +503,44 @@ mod tests {
|
||||
"the FUA entry point must be the one reached, with fua=true intact"
|
||||
);
|
||||
}
|
||||
|
||||
/// The `&mut dyn SectorSource` forwarding impl must delegate `set_speed`.
|
||||
///
|
||||
/// This one hides better than the read methods, because the trait's own
|
||||
/// default body is `fn set_speed(&mut self, _kbs: u16) {}` — so a forwarding
|
||||
/// impl that dropped the call on the floor compiles, type-checks, and looks
|
||||
/// exactly like a source that legitimately has no speed control. The
|
||||
/// consequence is not a wrong value but a silently absent one: the recovery
|
||||
/// path throttles a struggling drive by lowering its read speed, and a
|
||||
/// forwarder that swallowed the call would leave the drive at full speed
|
||||
/// through the damaged region while the caller believed it had slowed down.
|
||||
#[test]
|
||||
fn mut_ref_dyn_forwards_set_speed_to_the_inner_source() {
|
||||
let (mut spy, _reads, speeds, _bases) = Spy::new(0);
|
||||
let r: &mut dyn SectorSource = &mut spy;
|
||||
set_speed_generic(r, 5540);
|
||||
|
||||
assert_eq!(
|
||||
*speeds.lock().unwrap(),
|
||||
vec![5540],
|
||||
"the forwarding impl must pass set_speed through to the inner \
|
||||
source; swallowing it is indistinguishable from the trait default \
|
||||
and silently disables recovery-path throttling"
|
||||
);
|
||||
}
|
||||
|
||||
/// The same for `Box<dyn SectorSource>`, which is the receiver the mux read
|
||||
/// paths actually hold.
|
||||
#[test]
|
||||
fn boxed_dyn_forwards_set_speed_to_the_inner_source() {
|
||||
let (spy, _reads, speeds, _bases) = Spy::new(0);
|
||||
let b: Box<dyn SectorSource> = Box::new(spy);
|
||||
set_speed_generic(b, 11080);
|
||||
|
||||
assert_eq!(
|
||||
*speeds.lock().unwrap(),
|
||||
vec![11080],
|
||||
"the boxed forwarding impl must pass set_speed through unchanged"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user