use rusty_stack::core::plan::PlanItem;
use rusty_stack::core::types::ValidationTier;
use rusty_stack::core::verification::{VerificationCheck, VerificationResult};
use rusty_stack::orchestrator::apply::{ApplyEngine, ApplyExecutor, ApplyOptions, ApplyStatus};
use rusty_stack::orchestrator::planner::{PlannerItem, UpdateClassification};
use rusty_stack::orchestrator::verify::{VerifyExecutor, VerifyRunner, VerifyStatus};
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
struct MockApplyExecutor {
fail_for: HashSet<String>,
calls: Arc<Mutex<Vec<(String, String)>>>,
}
impl MockApplyExecutor {
fn new() -> Self {
Self {
fail_for: HashSet::new(),
calls: Arc::new(Mutex::new(Vec::new())),
}
}
fn with_failures(fail_ids: &[&str]) -> Self {
Self {
fail_for: fail_ids.iter().map(|s| s.to_string()).collect(),
calls: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl ApplyExecutor for MockApplyExecutor {
fn apply_component(&self, component_id: &str, proposed_version: &str) -> Result<(), String> {
self.calls
.lock()
.unwrap()
.push((component_id.to_string(), proposed_version.to_string()));
if self.fail_for.contains(component_id) {
Err(format!("mock failure for '{component_id}'"))
} else {
Ok(())
}
}
}
struct MockVerifyExecutor {
fail_for: HashSet<String>,
}
impl MockVerifyExecutor {
fn new() -> Self {
Self {
fail_for: HashSet::new(),
}
}
fn with_failures(fail_ids: &[&str]) -> Self {
Self {
fail_for: fail_ids.iter().map(|s| s.to_string()).collect(),
}
}
}
impl VerifyExecutor for MockVerifyExecutor {
fn verify_component(&self, component_id: &str, expected_version: &str) -> VerificationResult {
if self.fail_for.contains(component_id) {
VerificationResult::new(
component_id,
vec![
VerificationCheck::passed("binary_exists", "found"),
VerificationCheck::failed(
"version_check",
format!("expected {expected_version}, got 0.0.0"),
),
],
)
} else {
VerificationResult::new(
component_id,
vec![
VerificationCheck::passed("binary_exists", "found"),
VerificationCheck::passed("version_check", expected_version),
],
)
}
}
}
fn make_planner_item(
id: &str,
current: &str,
proposed: &str,
deps: Vec<&str>,
selected: bool,
classification: UpdateClassification,
) -> PlannerItem {
PlannerItem {
plan_item: PlanItem::new(
id,
current,
proposed,
ValidationTier::Validated,
selected,
"test",
deps.into_iter().map(|s| s.to_string()).collect(),
true,
),
classification,
visible: true,
selected,
classification_reason: "test".to_string(),
requires_hardware_check: false,
min_rocm_version: String::new(),
}
}
fn simple_item(id: &str, selected: bool) -> PlannerItem {
make_planner_item(
id,
"1.0.0",
"1.1.0",
vec![],
selected,
UpdateClassification::Safe,
)
}
fn item_with_deps(id: &str, deps: Vec<&str>, selected: bool) -> PlannerItem {
make_planner_item(
id,
"1.0.0",
"1.1.0",
deps,
selected,
UpdateClassification::Safe,
)
}
#[test]
fn test_integration_dependency_order_rocm_before_pytorch() {
let executor = MockApplyExecutor::new();
let calls = executor.calls.clone();
let items = vec![
item_with_deps("pytorch", vec!["rocm"], true),
simple_item("rocm", true),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 2);
assert_eq!(summary.failed.len(), 0);
let guard = calls.lock().unwrap();
let call_order: Vec<&str> = guard.iter().map(|(id, _)| id.as_str()).collect();
let rocm_pos = call_order.iter().position(|&id| id == "rocm").unwrap();
let pytorch_pos = call_order.iter().position(|&id| id == "pytorch").unwrap();
assert!(
rocm_pos < pytorch_pos,
"rocm must be applied before pytorch (rocm at {rocm_pos}, pytorch at {pytorch_pos})"
);
}
#[test]
fn test_integration_dependency_chain_ordering() {
let executor = MockApplyExecutor::new();
let calls = executor.calls.clone();
let items = vec![
item_with_deps("triton", vec!["pytorch"], true),
item_with_deps("pytorch", vec!["rocm"], true),
simple_item("rocm", true),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 3);
let guard = calls.lock().unwrap();
let call_order: Vec<&str> = guard.iter().map(|(id, _)| id.as_str()).collect();
let rocm_pos = call_order.iter().position(|&id| id == "rocm").unwrap();
let pytorch_pos = call_order.iter().position(|&id| id == "pytorch").unwrap();
let triton_pos = call_order.iter().position(|&id| id == "triton").unwrap();
assert!(rocm_pos < pytorch_pos, "rocm before pytorch");
assert!(pytorch_pos < triton_pos, "pytorch before triton");
}
#[test]
fn test_integration_failed_guarded_stops_dependents() {
let executor = MockApplyExecutor::with_failures(&["pytorch"]);
let items = vec![
make_planner_item(
"flash-attn",
"1.0.0",
"2.0.0",
vec!["pytorch"],
true,
UpdateClassification::Guarded,
),
make_planner_item(
"pytorch",
"2.4.0",
"2.5.0",
vec!["rocm"],
true,
UpdateClassification::Guarded,
),
make_planner_item(
"rocm",
"7.2.0",
"7.2.1",
vec![],
true,
UpdateClassification::Safe,
),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 1);
assert_eq!(summary.success[0].component_id, "rocm");
assert_eq!(summary.failed.len(), 1);
assert_eq!(summary.failed[0].component_id, "pytorch");
assert_eq!(summary.failed[0].status, ApplyStatus::Failed);
assert_eq!(summary.blocked.len(), 1);
assert_eq!(summary.blocked[0].component_id, "flash-attn");
assert_eq!(summary.blocked[0].status, ApplyStatus::BlockedByFailure);
}
#[test]
fn test_integration_unrelated_work_continues_after_failure() {
let executor = MockApplyExecutor::with_failures(&["pytorch"]);
let items = vec![
item_with_deps("triton", vec!["pytorch"], true),
simple_item("pytorch", true),
simple_item("wandb", true),
simple_item("rocm-smi", true),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.failed.len(), 1);
assert_eq!(summary.failed[0].component_id, "pytorch");
assert_eq!(summary.blocked.len(), 1);
assert_eq!(summary.blocked[0].component_id, "triton");
assert_eq!(summary.success.len(), 2);
let success_ids: HashSet<&str> = summary
.success
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(success_ids.contains("wandb"));
assert!(success_ids.contains("rocm-smi"));
}
#[test]
fn test_integration_verify_after_apply_success() {
let apply_summary = {
let executor = MockApplyExecutor::new();
let items = vec![
simple_item("rocm", true),
item_with_deps("pytorch", vec!["rocm"], true),
];
let engine = ApplyEngine::new(executor);
engine.apply(&items, &ApplyOptions::default())
};
assert_eq!(apply_summary.success.len(), 2);
let verifier = MockVerifyExecutor::new();
let runner = VerifyRunner::new(verifier);
let verify_summary = runner.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 2);
assert_eq!(verify_summary.failed.len(), 0);
for item in &verify_summary.verified {
assert!(item.verification_result.is_some());
assert!(item.verification_result.as_ref().unwrap().success);
}
}
#[test]
fn test_integration_verify_detects_failure() {
let apply_summary = {
let executor = MockApplyExecutor::new();
let items = vec![simple_item("rocm", true), simple_item("pytorch", true)];
let engine = ApplyEngine::new(executor);
engine.apply(&items, &ApplyOptions::default())
};
let verifier = MockVerifyExecutor::with_failures(&["pytorch"]);
let runner = VerifyRunner::new(verifier);
let verify_summary = runner.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 1);
assert_eq!(verify_summary.verified[0].component_id, "rocm");
assert_eq!(verify_summary.failed.len(), 1);
assert_eq!(verify_summary.failed[0].component_id, "pytorch");
assert_eq!(
verify_summary.failed[0].verified,
VerifyStatus::VerifyFailed
);
}
#[test]
fn test_integration_full_summary_partitioning() {
let executor = MockApplyExecutor::with_failures(&["pytorch"]);
let items = vec![
item_with_deps("triton", vec!["pytorch"], true),
simple_item("pytorch", true),
simple_item("wandb", true),
simple_item("mpi4py", false), ];
let engine = ApplyEngine::new(executor);
let apply_summary = engine.apply(&items, &ApplyOptions::default());
assert!(apply_summary.is_complete_partition());
assert_eq!(apply_summary.total(), 4);
assert_eq!(apply_summary.success.len(), 1); assert_eq!(apply_summary.failed.len(), 1); assert_eq!(apply_summary.blocked.len(), 1); assert_eq!(apply_summary.held_back.len(), 1);
let verifier = MockVerifyExecutor::new();
let runner = VerifyRunner::new(verifier);
let verify_summary = runner.verify(&apply_summary);
assert!(verify_summary.is_complete_partition());
assert_eq!(verify_summary.total(), 4);
assert_eq!(verify_summary.verified.len(), 1); assert_eq!(verify_summary.not_verified.len(), 3); }
#[test]
fn test_integration_user_untick_preselected() {
let executor = MockApplyExecutor::new();
let calls = executor.calls.clone();
let items = vec![
simple_item("rocm", true),
simple_item("pytorch", false), simple_item("triton", false), ];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 1);
assert_eq!(summary.success[0].component_id, "rocm");
assert_eq!(summary.held_back.len(), 2);
let held_ids: HashSet<&str> = summary
.held_back
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(held_ids.contains("pytorch"));
assert!(held_ids.contains("triton"));
let calls = calls.lock().unwrap();
assert_eq!(calls.len(), 1);
assert_eq!(calls[0].0, "rocm");
}
#[test]
fn test_integration_end_to_end_apply_verify() {
let apply_summary = {
let executor = MockApplyExecutor::with_failures(&["deepspeed"]);
let items = vec![
simple_item("rocm", true),
item_with_deps("pytorch", vec!["rocm"], true),
item_with_deps("deepspeed", vec!["pytorch"], true),
simple_item("wandb", true),
simple_item("comfyui", false), ];
let engine = ApplyEngine::new(executor);
engine.apply(&items, &ApplyOptions::default())
};
assert!(apply_summary.is_complete_partition());
assert_eq!(apply_summary.total(), 5);
assert_eq!(apply_summary.success.len(), 3); assert_eq!(apply_summary.failed.len(), 1); assert_eq!(apply_summary.held_back.len(), 1);
let verifier = MockVerifyExecutor::with_failures(&["wandb"]);
let runner = VerifyRunner::new(verifier);
let verify_summary = runner.verify(&apply_summary);
assert!(verify_summary.is_complete_partition());
assert_eq!(verify_summary.total(), 5);
assert_eq!(verify_summary.verified.len(), 2);
let verified_ids: HashSet<&str> = verify_summary
.verified
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(verified_ids.contains("rocm"));
assert!(verified_ids.contains("pytorch"));
assert_eq!(verify_summary.failed.len(), 1);
assert_eq!(verify_summary.failed[0].component_id, "wandb");
assert_eq!(verify_summary.not_verified.len(), 2);
let not_verified_ids: HashSet<&str> = verify_summary
.not_verified
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(not_verified_ids.contains("deepspeed"));
assert!(not_verified_ids.contains("comfyui"));
}
#[test]
fn test_integration_transitive_failure_propagation() {
let executor = MockApplyExecutor::with_failures(&["c"]);
let items = vec![
item_with_deps("a", vec!["b"], true),
item_with_deps("b", vec!["c"], true),
simple_item("c", true),
simple_item("unrelated", true),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.failed.len(), 1);
assert_eq!(summary.failed[0].component_id, "c");
assert_eq!(summary.blocked.len(), 2);
let blocked_ids: HashSet<&str> = summary
.blocked
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(blocked_ids.contains("a"));
assert!(blocked_ids.contains("b"));
assert_eq!(summary.success.len(), 1);
assert_eq!(summary.success[0].component_id, "unrelated");
}
#[test]
fn test_integration_diamond_dependency_failure_at_root() {
let executor = MockApplyExecutor::with_failures(&["rocm"]);
let items = vec![
item_with_deps("flash-attn", vec!["pytorch"], true),
item_with_deps("pytorch", vec!["rocm"], true),
item_with_deps("triton", vec!["pytorch"], true),
simple_item("rocm", true),
simple_item("wandb", true),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.failed.len(), 1);
assert_eq!(summary.failed[0].component_id, "rocm");
assert_eq!(summary.blocked.len(), 3);
let blocked_ids: HashSet<&str> = summary
.blocked
.iter()
.map(|i| i.component_id.as_str())
.collect();
assert!(blocked_ids.contains("pytorch"));
assert!(blocked_ids.contains("flash-attn"));
assert!(blocked_ids.contains("triton"));
assert_eq!(summary.success.len(), 1);
assert_eq!(summary.success[0].component_id, "wandb");
}
#[test]
fn test_integration_all_deselected_is_noop() {
let executor = MockApplyExecutor::new();
let calls = executor.calls.clone();
let items = vec![
simple_item("rocm", false),
simple_item("pytorch", false),
simple_item("triton", false),
];
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 0);
assert_eq!(summary.failed.len(), 0);
assert_eq!(summary.held_back.len(), 3);
assert!(calls.lock().unwrap().is_empty());
}