226 lines
8.1 KiB
Rust
226 lines
8.1 KiB
Rust
|
|
//! Black-box contract tests for MVP stage-local weight lifecycle.
|
||
|
|
//!
|
||
|
|
//! These tests intentionally know only the public weight-work surface:
|
||
|
|
//!
|
||
|
|
//! - `ProvisionStage` assignment in
|
||
|
|
//! - artifact, parse, allocation, binding, and cache outcomes in
|
||
|
|
//! - `WeightsReady`, `StageReady`, and `StageFault` out
|
||
|
|
//!
|
||
|
|
//! They assert the guarantees in
|
||
|
|
//! `specs/mvp_system/weight_lifecycle_contract.md`.
|
||
|
|
|
||
|
|
use mvp_system::weight_lifecycle as weights;
|
||
|
|
|
||
|
|
// A valid assignment gives the stage exactly one layer range and one source.
|
||
|
|
// Tests vary only source or failure outcome so the assignment contract remains
|
||
|
|
// visible.
|
||
|
|
fn valid_assignment() -> weights::WeightAssignment {
|
||
|
|
weights::WeightAssignment {
|
||
|
|
run_id: weights::RunId(7),
|
||
|
|
stage_index: 1,
|
||
|
|
plan_layer_range: weights::LayerRange {
|
||
|
|
start: 12,
|
||
|
|
end_exclusive: 24,
|
||
|
|
},
|
||
|
|
assigned_layer_range: weights::LayerRange {
|
||
|
|
start: 12,
|
||
|
|
end_exclusive: 24,
|
||
|
|
},
|
||
|
|
source: weights::WeightSource::WholeGguf {
|
||
|
|
uri: "test://model.gguf".into(),
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Weight loading is intentionally opaque. The harness accepts public loader and
|
||
|
|
// worker outcomes and records only stage-visible events and commands.
|
||
|
|
fn new_weight_harness() -> weights::WeightLifecycleHarness {
|
||
|
|
weights::WeightLifecycleHarness::new(weights::NodeId(11))
|
||
|
|
}
|
||
|
|
|
||
|
|
// The success facts represent the observable prerequisites for WeightsReady:
|
||
|
|
// artifact bytes exist, the assigned layer range is valid, and the worker has
|
||
|
|
// loaded or bound that range.
|
||
|
|
fn successful_load_events() -> Vec<weights::WeightEvent> {
|
||
|
|
vec![
|
||
|
|
weights::WeightEvent::ArtifactAvailable {
|
||
|
|
bytes: weights::ArtifactBytes::Local,
|
||
|
|
},
|
||
|
|
weights::WeightEvent::LayerRangeValidated,
|
||
|
|
weights::WeightEvent::WorkerRangeBound,
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Each failure case maps one public loader or worker failure to the stable
|
||
|
|
// stage fault reason expected at the control boundary.
|
||
|
|
fn failure_cases() -> Vec<(weights::WeightEvent, weights::StageFaultReason)> {
|
||
|
|
vec![
|
||
|
|
(
|
||
|
|
weights::WeightEvent::DownloadFailed,
|
||
|
|
weights::StageFaultReason::WeightDownloadFailed,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
weights::WeightEvent::ParseFailed,
|
||
|
|
weights::StageFaultReason::WeightParseFailed,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
weights::WeightEvent::DeviceAllocationFailed,
|
||
|
|
weights::StageFaultReason::DeviceAllocationFailed,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
weights::WeightEvent::BindingFailed,
|
||
|
|
weights::StageFaultReason::WeightBindingFailed,
|
||
|
|
),
|
||
|
|
(
|
||
|
|
weights::WeightEvent::InvalidLayerRange,
|
||
|
|
weights::StageFaultReason::InvalidLayerRange,
|
||
|
|
),
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
// This proves a stage receives weight source and exactly one assigned layer
|
||
|
|
// range from provisioning, validates it against the plan, and does not claim
|
||
|
|
// graph-visible ownership outside that range.
|
||
|
|
#[test]
|
||
|
|
fn assignment_is_stage_local_and_range_limited() {
|
||
|
|
// Start weight work from the provisioned assignment.
|
||
|
|
let mut harness = new_weight_harness();
|
||
|
|
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
|
||
|
|
|
||
|
|
// The load command may use the physical source, but its graph-visible layer
|
||
|
|
// range must be the assigned range.
|
||
|
|
for command in harness.commands() {
|
||
|
|
if let weights::WeightCommand::LoadOrBindRange { range, .. } = command {
|
||
|
|
assert_eq!(
|
||
|
|
*range,
|
||
|
|
weights::LayerRange {
|
||
|
|
start: 12,
|
||
|
|
end_exclusive: 24,
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// There must be no command claiming ownership of neighboring layers.
|
||
|
|
assert!(!harness.commands().iter().any(|command| {
|
||
|
|
matches!(
|
||
|
|
command,
|
||
|
|
weights::WeightCommand::AdvertiseLoadedLayerRange {
|
||
|
|
range,
|
||
|
|
..
|
||
|
|
} if range.start < 12 || range.end_exclusive > 24
|
||
|
|
)
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
// This proves whole GGUF download, shard download, and cache use are physical
|
||
|
|
// mechanisms with the same public outcome: WeightsReady or StageFault.
|
||
|
|
#[test]
|
||
|
|
fn supported_physical_sources_have_same_visible_success_contract() {
|
||
|
|
// Exercise every supported source without asserting how bytes are obtained.
|
||
|
|
let sources = vec![
|
||
|
|
weights::WeightSource::WholeGguf {
|
||
|
|
uri: "test://model.gguf".into(),
|
||
|
|
},
|
||
|
|
weights::WeightSource::ShardSet {
|
||
|
|
uris: vec!["test://model.layers.12-24.gguf".into()],
|
||
|
|
},
|
||
|
|
weights::WeightSource::CachedArtifact {
|
||
|
|
cache_key: "model:layers:12-24".into(),
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
for source in sources {
|
||
|
|
// Install the source in an otherwise valid assignment.
|
||
|
|
let mut assignment = valid_assignment();
|
||
|
|
assignment.source = source;
|
||
|
|
let mut harness = new_weight_harness();
|
||
|
|
harness.observe(weights::WeightEvent::Provisioned(assignment));
|
||
|
|
|
||
|
|
// Drive the same public success facts for every source.
|
||
|
|
for event in successful_load_events() {
|
||
|
|
harness.observe(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
// The system-visible success outcome is WeightsReady.
|
||
|
|
assert!(harness.events().iter().any(|event| {
|
||
|
|
matches!(
|
||
|
|
event,
|
||
|
|
weights::WeightLifecycleEvent::WeightsReady {
|
||
|
|
run_id: weights::RunId(7),
|
||
|
|
stage_index: 1,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// This proves WeightsReady requires artifact availability, layer validation,
|
||
|
|
// and worker bind/load completion, and that WeightsReady precedes StageReady.
|
||
|
|
#[test]
|
||
|
|
fn weights_ready_requires_all_weight_facts_and_precedes_stage_ready() {
|
||
|
|
// Start from a valid assignment.
|
||
|
|
let mut harness = new_weight_harness();
|
||
|
|
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
|
||
|
|
|
||
|
|
// Feed every success fact except the final one and prove no prefix is
|
||
|
|
// sufficient for WeightsReady.
|
||
|
|
let mut events = successful_load_events();
|
||
|
|
let final_event = events.pop().expect("fixture has final weight event");
|
||
|
|
for event in events {
|
||
|
|
harness.observe(event);
|
||
|
|
assert!(!harness.events().iter().any(|event| {
|
||
|
|
matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. })
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
// The final weight prerequisite emits WeightsReady.
|
||
|
|
harness.observe(final_event);
|
||
|
|
let weights_ready_pos = harness
|
||
|
|
.events()
|
||
|
|
.iter()
|
||
|
|
.position(|event| matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. }))
|
||
|
|
.expect("WeightsReady must be emitted");
|
||
|
|
|
||
|
|
// StageReady may occur only after the StageController observes WeightsReady
|
||
|
|
// and the other local setup prerequisites.
|
||
|
|
harness.observe(weights::WeightEvent::OtherStagePrerequisitesReady);
|
||
|
|
let stage_ready_pos = harness
|
||
|
|
.events()
|
||
|
|
.iter()
|
||
|
|
.position(|event| matches!(event, weights::WeightLifecycleEvent::StageReady { .. }))
|
||
|
|
.expect("StageReady must be emitted after prerequisites");
|
||
|
|
assert!(weights_ready_pos < stage_ready_pos);
|
||
|
|
}
|
||
|
|
|
||
|
|
// This proves every weight failure source faults the stage and suppresses both
|
||
|
|
// WeightsReady and StageReady.
|
||
|
|
#[test]
|
||
|
|
fn weight_failures_emit_stage_fault_without_readiness() {
|
||
|
|
// Each failure source gets an isolated attempt.
|
||
|
|
for (failure_event, expected_reason) in failure_cases() {
|
||
|
|
let mut harness = new_weight_harness();
|
||
|
|
harness.observe(weights::WeightEvent::Provisioned(valid_assignment()));
|
||
|
|
|
||
|
|
// Deliver the public failure outcome from weight work.
|
||
|
|
harness.observe(failure_event);
|
||
|
|
|
||
|
|
// The stable fault reason must be observable.
|
||
|
|
assert!(harness.events().iter().any(|event| {
|
||
|
|
matches!(
|
||
|
|
event,
|
||
|
|
weights::WeightLifecycleEvent::StageFault {
|
||
|
|
reason,
|
||
|
|
..
|
||
|
|
} if *reason == expected_reason
|
||
|
|
)
|
||
|
|
}));
|
||
|
|
|
||
|
|
// Readiness cannot also be emitted after a faulted weight attempt.
|
||
|
|
assert!(!harness.events().iter().any(|event| {
|
||
|
|
matches!(event, weights::WeightLifecycleEvent::WeightsReady { .. })
|
||
|
|
|| matches!(event, weights::WeightLifecycleEvent::StageReady { .. })
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
}
|