use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use rusty_stack::core::manifest::*;
use rusty_stack::core::plan::*;
use rusty_stack::core::telemetry_types::*;
use rusty_stack::core::types::*;
use rusty_stack::core::verification::*;
use rusty_stack::orchestrator::apply::*;
use rusty_stack::orchestrator::planner::*;
use rusty_stack::orchestrator::verify::*;
use rusty_stack::telemetry::opt_in::*;
fn make_mc(id: &str, version: &str, tier: ValidationTier) -> ManifestComponent {
ManifestComponent {
id: id.to_string(),
version: version.to_string(),
script: format!("install_{id}.sh"),
category: Category::Core,
validation_tier: tier,
min_rocm_version: String::new(),
compatible_channels: vec![],
dependencies: vec![],
exclusive_group: String::new(),
}
}
fn make_manifest(components: Vec<ManifestComponent>) -> Manifest {
Manifest {
schema_version: CURRENT_SCHEMA_VERSION,
sequence: 1,
generated_at: String::new(),
expires_at: None,
min_runtime_version: String::new(),
components,
signature: None,
}
}
fn make_signed_manifest(components: Vec<ManifestComponent>, sequence: u64) -> Manifest {
create_signed_manifest(CURRENT_SCHEMA_VERSION, sequence, None, components)
}
fn make_context() -> CompatibilityContext {
let mut ctx = CompatibilityContext::new();
ctx.rocm_version = "7.2.1".to_string();
ctx.rocm_channel = "latest".to_string();
ctx.gpu_architecture = "gfx1100".to_string();
ctx.available_executors = HashSet::from([
ExecutorKind::LegacyScript,
ExecutorKind::Rust,
ExecutorKind::ExternalPackageManager,
]);
ctx.runtime_version = "1.0.0".to_string();
ctx
}
fn make_planner_item(
id: &str,
current: &str,
proposed: &str,
deps: Vec<&str>,
selected: bool,
tier: ValidationTier,
) -> PlannerItem {
PlannerItem {
plan_item: PlanItem::new(PlanItemInput {
component_id: id.to_string(),
current_version: current.to_string(),
proposed_version: proposed.to_string(),
validation_tier: tier,
selected,
rationale: "test".to_string(),
dependencies: deps.into_iter().map(|s| s.to_string()).collect(),
isolation_safe: true,
}),
classification: UpdateClassification::Safe,
visible: true,
selected,
classification_reason: "test".to_string(),
requires_hardware_check: false,
min_rocm_version: String::new(),
}
}
#[derive(Debug, Default)]
struct TrackingExecutor {
calls: Arc<Mutex<Vec<(String, String)>>>,
}
impl TrackingExecutor {
fn new() -> Self {
Self {
calls: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl ApplyExecutor for TrackingExecutor {
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()));
Ok(())
}
}
struct SelectiveFailExecutor {
fail_ids: HashSet<String>,
calls: Arc<Mutex<Vec<String>>>,
}
impl SelectiveFailExecutor {
fn new(fail_ids: Vec<&str>) -> Self {
Self {
fail_ids: fail_ids.into_iter().map(|s| s.to_string()).collect(),
calls: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl ApplyExecutor for SelectiveFailExecutor {
fn apply_component(&self, component_id: &str, _proposed_version: &str) -> Result<(), String> {
self.calls.lock().unwrap().push(component_id.to_string());
if self.fail_ids.contains(component_id) {
Err(format!("forced failure for '{}'", component_id))
} else {
Ok(())
}
}
}
struct MockVerifyExecutor {
fail_ids: HashSet<String>,
}
impl MockVerifyExecutor {
fn new(fail_ids: Vec<&str>) -> Self {
Self {
fail_ids: fail_ids.into_iter().map(|s| s.to_string()).collect(),
}
}
}
impl VerifyExecutor for MockVerifyExecutor {
fn verify_component(&self, component_id: &str, version: &str) -> VerificationResult {
if self.fail_ids.contains(component_id) {
VerificationResult::new(
component_id,
vec![VerificationCheck::failed(
"import_test",
format!("import {} failed", component_id),
)],
)
} else {
VerificationResult::new(
component_id,
vec![
VerificationCheck::passed("binary_exists", "found"),
VerificationCheck::passed("version_matches", version),
],
)
}
}
}
#[test]
fn test_integration_full_lifecycle_scan_plan_apply_verify_report() {
let mut context = make_context();
context
.installed_versions
.insert("rocm".to_string(), "7.2.0".to_string());
context.installed_components.insert("rocm".to_string());
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
context
.installed_versions
.insert("triton".to_string(), "3.0.0".to_string());
context.installed_components.insert("triton".to_string());
let manifest = make_manifest(vec![
make_mc("rocm", "7.2.1", ValidationTier::Validated),
make_mc("pytorch", "2.4.1", ValidationTier::Validated),
make_mc("triton", "3.0.1", ValidationTier::Validated),
]);
let planner = UpdatePlanner::new();
let options = PlannerOptions::default();
let plan_items = planner
.build_plan(&manifest, &context, &options)
.expect("plan should succeed");
assert!(!plan_items.is_empty(), "scan should detect components");
let rocm_item = plan_items
.iter()
.find(|i| i.plan_item.component_id == "rocm")
.expect("rocm should be in plan");
assert_eq!(rocm_item.classification, UpdateClassification::Safe);
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let _apply_opts = ApplyOptions {
verify_post_install_version: false,
..ApplyOptions::default()
};
let apply_summary = engine.apply(&plan_items, &_apply_opts);
assert_eq!(apply_summary.success.len(), 3, "all 3 should succeed");
assert_eq!(apply_summary.failed.len(), 0, "no failures");
assert!(apply_summary.is_complete_partition());
let verifier = VerifyRunner::new_noop();
let verify_summary = verifier.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 3, "all 3 verified");
assert!(verify_summary.is_complete_partition());
let total = verify_summary.total();
assert_eq!(total, 3, "total should be 3");
for item in &verify_summary.verified {
assert!(item.verification_result.is_some());
assert!(item.verification_result.as_ref().unwrap().success);
assert_eq!(item.verified, VerifyStatus::Verified);
}
}
#[test]
fn test_integration_lifecycle_failure_at_apply_halts_pipeline() {
let items = vec![
make_planner_item(
"a",
"1.0.0",
"1.1.0",
vec!["b"],
true,
ValidationTier::Validated,
),
make_planner_item(
"b",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
make_planner_item(
"c",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
];
let executor = SelectiveFailExecutor::new(vec!["b"]);
let engine = ApplyEngine::new(executor);
let apply_summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(apply_summary.failed.len(), 1);
assert_eq!(apply_summary.blocked.len(), 1);
assert_eq!(apply_summary.success.len(), 1);
assert!(apply_summary.has_failures());
let verifier = VerifyRunner::new_noop();
let verify_summary = verifier.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 1);
assert_eq!(verify_summary.verified[0].component_id, "c");
}
#[test]
fn test_integration_upgrade_then_update_sees_newer_manifest() {
let pre_manifest = make_signed_manifest(
vec![make_mc("pytorch", "2.4.0", ValidationTier::Validated)],
1,
);
let post_manifest = make_signed_manifest(
vec![make_mc("pytorch", "2.5.0", ValidationTier::Validated)],
10, );
let pre_pytorch = pre_manifest
.components
.iter()
.find(|c| c.id == "pytorch")
.unwrap();
let post_pytorch = post_manifest
.components
.iter()
.find(|c| c.id == "pytorch")
.unwrap();
assert!(post_manifest.sequence > pre_manifest.sequence);
assert!(post_pytorch.version > pre_pytorch.version);
let mut context = make_context();
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&post_manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let pytorch_plan = plan
.iter()
.find(|i| i.plan_item.component_id == "pytorch")
.unwrap();
assert_eq!(pytorch_plan.plan_item.proposed_version, "2.5.0");
}
struct TestFetcher {
remote: Option<Manifest>,
cached: Option<Manifest>,
}
impl ManifestFetcher for TestFetcher {
fn fetch_remote(&self) -> Option<Manifest> {
self.remote.clone()
}
fn load_cached(&self) -> Option<Manifest> {
self.cached.clone()
}
}
#[test]
fn test_integration_manifest_trust_chain_fresh_remote() {
let remote = make_signed_manifest(
vec![make_mc("pytorch", "2.5.0", ValidationTier::Validated)],
10,
);
let fetcher = TestFetcher {
remote: Some(remote),
cached: None,
};
let resolved = resolve_manifest(&fetcher);
assert_eq!(resolved.source, ManifestSource::FreshRemote);
}
#[test]
fn test_integration_manifest_trust_chain_corrupted_to_cached() {
let mut bad_remote = make_signed_manifest(
vec![make_mc("pytorch", "2.5.0", ValidationTier::Validated)],
10,
);
bad_remote.signature = Some("corrupt".to_string());
let cached = make_signed_manifest(
vec![make_mc("pytorch", "2.4.0", ValidationTier::Validated)],
5,
);
let fetcher = TestFetcher {
remote: Some(bad_remote),
cached: Some(cached),
};
let resolved = resolve_manifest(&fetcher);
assert_eq!(resolved.source, ManifestSource::CachedRemote);
}
#[test]
fn test_integration_manifest_trust_chain_baseline_fallback() {
let fetcher = TestFetcher {
remote: None,
cached: None,
};
let resolved = resolve_manifest(&fetcher);
assert_eq!(resolved.source, ManifestSource::Baseline);
assert!(!resolved.manifest.components.is_empty());
}
#[test]
fn test_integration_manifest_overlay_merges_cleanly() {
let baseline = Manifest::load_baseline().expect("baseline loads");
let baseline_count = baseline.components.len();
let overlay = ManifestOverlay {
components: vec![ManifestComponent {
id: "test-new-comp".to_string(),
version: "1.0.0".to_string(),
script: "install_test.sh".to_string(),
category: Category::Extension,
validation_tier: ValidationTier::Candidate,
min_rocm_version: String::new(),
compatible_channels: vec![],
dependencies: vec![],
exclusive_group: String::new(),
}],
};
let merged = overlay.merge_onto(&baseline);
assert_eq!(merged.components.len(), baseline_count + 1);
for orig in &baseline.components {
let merged_comp = merged.components.iter().find(|c| c.id == orig.id).unwrap();
assert_eq!(merged_comp.version, orig.version);
}
}
#[test]
fn test_integration_validation_tier_identical_across_stages() {
let tier = ValidationTier::Candidate;
let manifest_comp = make_mc("wandb", "0.18.0", tier);
assert_eq!(manifest_comp.validation_tier, tier);
let mut context = make_context();
context
.installed_versions
.insert("wandb".to_string(), "0.17.0".to_string());
context.installed_components.insert("wandb".to_string());
let manifest = make_manifest(vec![manifest_comp]);
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let plan_item = plan
.iter()
.find(|i| i.plan_item.component_id == "wandb")
.unwrap();
assert_eq!(
plan_item.plan_item.validation_tier, tier,
"tier must match at plan stage"
);
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let _apply_opts = ApplyOptions {
verify_post_install_version: false,
..ApplyOptions::default()
};
let apply_summary = engine.apply(&plan, &_apply_opts);
let wandb_applied = apply_summary
.success
.iter()
.find(|i| i.component_id == "wandb");
if let Some(_applied) = wandb_applied {
let verifier = VerifyRunner::new_noop();
let verify_summary = verifier.verify(&apply_summary);
let verify_item = verify_summary
.verified
.iter()
.find(|i| i.component_id == "wandb")
.expect("wandb should be verified when applied");
assert!(verify_item.verification_result.is_some());
assert!(verify_item.verification_result.as_ref().unwrap().success);
} else {
let is_held_back = apply_summary
.held_back
.iter()
.any(|i| i.component_id == "wandb");
assert!(
is_held_back
|| apply_summary
.success
.iter()
.any(|i| i.component_id == "wandb"),
"wandb should be either held back or applied, got: success={}, failed={}, held_back={}",
apply_summary.success.len(),
apply_summary.failed.len(),
apply_summary.held_back.len(),
);
}
assert_eq!(plan_item.plan_item.validation_tier, tier);
}
#[test]
fn test_integration_blocked_tier_propagates_to_all_stages() {
let tier = ValidationTier::Blocked;
let manifest_comp = make_mc("bad-pkg", "1.0.0", tier);
let mut context = make_context();
context
.installed_versions
.insert("bad-pkg".to_string(), "0.9.0".to_string());
context.installed_components.insert("bad-pkg".to_string());
let manifest = make_manifest(vec![manifest_comp]);
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let plan_item = plan
.iter()
.find(|i| i.plan_item.component_id == "bad-pkg")
.unwrap();
assert_eq!(plan_item.plan_item.validation_tier, tier);
assert_eq!(plan_item.classification, UpdateClassification::Blocked);
assert!(!plan_item.visible);
assert!(!plan_item.selected);
}
#[test]
fn test_integration_telemetry_consumes_verification_evidence() {
let items = vec![
make_planner_item(
"rocm",
"7.2.0",
"7.2.1",
vec![],
true,
ValidationTier::Validated,
),
make_planner_item(
"pytorch",
"2.4.0",
"2.4.1",
vec!["rocm"],
true,
ValidationTier::Validated,
),
];
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let apply_summary = engine.apply(&items, &ApplyOptions::default());
let verifier = VerifyRunner::new_noop();
let verify_summary = verifier.verify(&apply_summary);
let mut update_results = Vec::new();
for item in &verify_summary.verified {
if let Some(ref result) = item.verification_result {
update_results.push(serde_json::json!({
"component_id": item.component_id,
"verified": result.success,
"check_count": result.checks.len(),
}));
}
}
for item in &verify_summary.not_verified {
update_results.push(serde_json::json!({
"component_id": item.component_id,
"verified": false,
"reason": format!("{:?}", item.apply_status),
}));
}
let payload = TelemetryPayload::new("gfx1100", "AMD GPU", "7.2.1", 1, 16, 54.0);
assert!(payload.validate_no_personal_data().is_ok());
assert_eq!(update_results.len(), 2);
for result in &update_results {
assert!(result.get("component_id").is_some());
assert!(result.get("verified").is_some());
}
}
#[test]
fn test_integration_telemetry_verify_failure_included() {
let items = vec![make_planner_item(
"a",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
)];
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let apply_summary = engine.apply(&items, &ApplyOptions::default());
let verifier = VerifyRunner::new(MockVerifyExecutor::new(vec!["a"]));
let verify_summary = verifier.verify(&apply_summary);
assert_eq!(verify_summary.failed.len(), 1);
assert_eq!(verify_summary.failed[0].component_id, "a");
assert_eq!(
verify_summary.failed[0].verified,
VerifyStatus::VerifyFailed
);
let result = verify_summary.failed[0]
.verification_result
.as_ref()
.unwrap();
assert!(!result.success);
assert!(result
.checks
.iter()
.any(|c| c.status == CheckStatus::Failed));
}
#[test]
fn test_integration_shared_core_types_identical_across_platforms() {
let manifest = make_manifest(vec![
make_mc("rocm", "7.2.1", ValidationTier::Validated),
make_mc("pytorch", "2.5.0", ValidationTier::Validated),
]);
let mut context = make_context();
context
.installed_versions
.insert("rocm".to_string(), "7.2.0".to_string());
context.installed_components.insert("rocm".to_string());
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let json1 = serde_json::to_string(&plan).unwrap();
let json2 = serde_json::to_string(&plan).unwrap();
assert_eq!(json1, json2, "planner output must be deterministic");
let component =
ComponentIdentity::new("test-comp", "1.0.0", "install.sh", Category::Core).unwrap();
let serialized = serde_json::to_string(&component).unwrap();
let deserialized: ComponentIdentity = serde_json::from_str(&serialized).unwrap();
assert_eq!(component, deserialized);
}
#[test]
fn test_integration_rust_version_lookup_returns_structured_output() {
use rusty_stack::orchestrator::migration::migrated_get_version;
let version = migrated_get_version("nonexistent-component-xyz");
assert_eq!(
version, "unknown",
"unknown component should return 'unknown'"
);
let version = migrated_get_version("rocm");
assert!(!version.is_empty() || version == "not installed" || version == "unknown");
}
#[test]
fn test_integration_selection_plan_parity() {
use rusty_stack::orchestrator::migration::migrated_build_selection_plan;
let manifest = make_manifest(vec![
make_mc("rocm", "7.2.1", ValidationTier::Validated),
make_mc("pytorch", "2.5.0", ValidationTier::Validated),
]);
let mut context = make_context();
context
.installed_versions
.insert("rocm".to_string(), "7.2.0".to_string());
context.installed_components.insert("rocm".to_string());
let plan = migrated_build_selection_plan(&manifest, &context, &PlannerOptions::default())
.expect("selection plan should succeed");
assert!(!plan.is_empty(), "selection plan should not be empty");
let ids: Vec<&str> = plan
.iter()
.map(|i| i.plan_item.component_id.as_str())
.collect();
assert!(ids.contains(&"rocm"));
assert!(ids.contains(&"pytorch"));
}
#[test]
fn test_integration_dependency_ordering_across_stages() {
let mut context = make_context();
context
.installed_versions
.insert("rocm".to_string(), "7.2.0".to_string());
context.installed_components.insert("rocm".to_string());
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
context
.installed_versions
.insert("triton".to_string(), "3.0.0".to_string());
context.installed_components.insert("triton".to_string());
let manifest = make_manifest(vec![
make_mc("rocm", "7.2.1", ValidationTier::Validated),
make_mc("pytorch", "2.4.1", ValidationTier::Validated),
make_mc("triton", "3.0.1", ValidationTier::Validated),
]);
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let triton = plan
.iter()
.find(|i| i.plan_item.component_id == "triton")
.unwrap();
assert!(triton
.plan_item
.dependencies
.contains(&"pytorch".to_string()));
let pytorch = plan
.iter()
.find(|i| i.plan_item.component_id == "pytorch")
.unwrap();
assert!(pytorch.plan_item.dependencies.contains(&"rocm".to_string()));
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let _apply_opts = ApplyOptions {
verify_post_install_version: false,
..ApplyOptions::default()
};
let apply_summary = engine.apply(&plan, &_apply_opts);
assert_eq!(apply_summary.success.len(), 3);
let success_order: Vec<&str> = apply_summary
.success
.iter()
.map(|i| i.component_id.as_str())
.collect();
let rocm_pos = success_order.iter().position(|&id| id == "rocm").unwrap();
let pytorch_pos = success_order
.iter()
.position(|&id| id == "pytorch")
.unwrap();
let triton_pos = success_order.iter().position(|&id| id == "triton").unwrap();
assert!(rocm_pos < pytorch_pos, "rocm must be before pytorch");
assert!(pytorch_pos < triton_pos, "pytorch must be before triton");
let verifier = VerifyRunner::new_noop();
let verify_summary = verifier.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 3);
}
#[test]
fn test_integration_circular_deps_detected_at_plan_stage() {
let result = std::panic::catch_unwind(|| {
Plan::new(vec![
PlanItem::new(PlanItemInput {
component_id: "a".to_string(),
current_version: "1.0.0".to_string(),
proposed_version: "1.1.0".to_string(),
validation_tier: ValidationTier::Validated,
selected: true,
rationale: "".to_string(),
dependencies: vec!["b".to_string()],
isolation_safe: true,
}),
PlanItem::new(PlanItemInput {
component_id: "b".to_string(),
current_version: "1.0.0".to_string(),
proposed_version: "1.1.0".to_string(),
validation_tier: ValidationTier::Validated,
selected: true,
rationale: "".to_string(),
dependencies: vec!["a".to_string()],
isolation_safe: true,
}),
])
});
assert!(
result.is_err(),
"circular dependencies must be detected at plan stage"
);
}
#[test]
fn test_integration_diamond_dependency_correct() {
let items = vec![
make_planner_item(
"d",
"1.0.0",
"1.1.0",
vec!["b", "c"],
true,
ValidationTier::Validated,
),
make_planner_item(
"c",
"1.0.0",
"1.1.0",
vec!["a"],
true,
ValidationTier::Validated,
),
make_planner_item(
"b",
"1.0.0",
"1.1.0",
vec!["a"],
true,
ValidationTier::Validated,
),
make_planner_item(
"a",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
];
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let summary = engine.apply(&items, &ApplyOptions::default());
assert_eq!(summary.success.len(), 4);
let order: Vec<&str> = summary
.success
.iter()
.map(|i| i.component_id.as_str())
.collect();
let a_pos = order.iter().position(|&id| id == "a").unwrap();
let b_pos = order.iter().position(|&id| id == "b").unwrap();
let c_pos = order.iter().position(|&id| id == "c").unwrap();
let d_pos = order.iter().position(|&id| id == "d").unwrap();
assert!(a_pos < b_pos, "a before b");
assert!(a_pos < c_pos, "a before c");
assert!(b_pos < d_pos, "b before d");
assert!(c_pos < d_pos, "c before d");
}
#[test]
fn test_integration_failure_propagation_apply_to_report() {
let items = vec![
make_planner_item(
"a",
"1.0.0",
"1.1.0",
vec!["b"],
true,
ValidationTier::Validated,
),
make_planner_item(
"b",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
make_planner_item(
"c",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
];
let executor = SelectiveFailExecutor::new(vec!["b"]);
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, "b");
assert_eq!(summary.failed[0].status, ApplyStatus::Failed);
assert!(
!summary.failed[0].error_message.is_empty(),
"failure must have error message"
);
assert_eq!(summary.blocked.len(), 1);
assert_eq!(summary.blocked[0].component_id, "a");
assert_eq!(summary.blocked[0].status, ApplyStatus::BlockedByFailure);
assert!(
summary.blocked[0].error_message.contains("b"),
"must reference blocker"
);
assert_eq!(summary.success.len(), 1);
assert_eq!(summary.success[0].component_id, "c");
assert!(summary.is_complete_partition());
assert!(summary.has_failures());
}
#[test]
fn test_integration_verify_failure_visible_in_telemetry() {
let items = vec![
make_planner_item(
"a",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
make_planner_item(
"b",
"1.0.0",
"1.1.0",
vec![],
true,
ValidationTier::Validated,
),
];
let executor = TrackingExecutor::new();
let engine = ApplyEngine::new(executor);
let apply_summary = engine.apply(&items, &ApplyOptions::default());
let verifier = VerifyRunner::new(MockVerifyExecutor::new(vec!["b"]));
let verify_summary = verifier.verify(&apply_summary);
assert_eq!(verify_summary.verified.len(), 1);
assert_eq!(verify_summary.failed.len(), 1);
assert_eq!(verify_summary.failed[0].component_id, "b");
assert_eq!(
verify_summary.failed[0].verified,
VerifyStatus::VerifyFailed
);
let mut telemetry_items = Vec::new();
for item in &verify_summary.verified {
telemetry_items.push(serde_json::json!({
"component_id": item.component_id,
"verify_status": "verified",
}));
}
for item in &verify_summary.failed {
telemetry_items.push(serde_json::json!({
"component_id": item.component_id,
"verify_status": "failed",
}));
}
let failed_entry = telemetry_items
.iter()
.find(|t| t["component_id"] == "b")
.expect("b must be in telemetry");
assert_eq!(failed_entry["verify_status"], "failed");
}
#[test]
fn test_integration_tui_types_still_functional() {
let component = rusty_stack::core::types::Component {
id: "pytorch".to_string(),
name: "PyTorch with ROCm".to_string(),
description: "Deep learning framework".to_string(),
script: "install_pytorch_rocm.sh".to_string(),
category: Category::Core,
required: true,
selected: true,
installed: false,
progress: 0.0,
estimate: "10-15 min".to_string(),
needs_sudo: true,
experimental: false,
note: None,
};
let json = serde_json::to_string(&component).unwrap();
let back: rusty_stack::core::types::Component = serde_json::from_str(&json).unwrap();
assert_eq!(component, back);
assert_eq!(back.category, Category::Core);
assert_eq!(Category::all().len(), 7);
assert_eq!(Stage::all().len(), 11);
}
#[test]
fn test_integration_manifest_filters_by_platform_context() {
let legacy_only = ManifestComponent {
id: "legacy-tool".to_string(),
version: "1.0.0".to_string(),
script: "install_legacy.sh".to_string(),
category: Category::Extension,
validation_tier: ValidationTier::Validated,
min_rocm_version: String::new(),
compatible_channels: vec!["legacy".to_string()],
dependencies: vec![],
exclusive_group: String::new(),
};
let manifest = make_manifest(vec![
make_mc("pytorch", "2.5.0", ValidationTier::Validated),
legacy_only,
]);
let mut context = make_context();
context.rocm_channel = "latest".to_string();
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
let planner = UpdatePlanner::new();
let plan = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let legacy_item = plan
.iter()
.find(|i| i.plan_item.component_id == "legacy-tool")
.expect("legacy-tool should be in plan");
assert!(
legacy_item.classification == UpdateClassification::Blocked
|| legacy_item.classification == UpdateClassification::Candidate,
"legacy-tool should be blocked or candidate on latest channel, got {:?}",
legacy_item.classification
);
context.rocm_channel = "legacy".to_string();
let plan_legacy = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let legacy_item = plan_legacy
.iter()
.find(|i| i.plan_item.component_id == "legacy-tool");
if let Some(item) = legacy_item {
assert_ne!(item.classification, UpdateClassification::Blocked);
}
}
#[derive(Debug)]
pub struct OperationLock {
lock_path: std::path::PathBuf,
acquired: bool,
}
impl OperationLock {
pub fn new(lock_path: std::path::PathBuf) -> Self {
Self {
lock_path,
acquired: false,
}
}
pub fn try_acquire(&mut self) -> Result<bool, String> {
if self.lock_path.exists() {
if let Ok(metadata) = std::fs::metadata(&self.lock_path) {
if let Ok(modified) = metadata.modified() {
let elapsed = modified.elapsed().unwrap_or_default();
if elapsed.as_secs() > 600 {
let _ = std::fs::remove_file(&self.lock_path);
} else {
return Ok(false); }
}
}
}
if let Some(parent) = self.lock_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
std::fs::write(&self.lock_path, format!("pid={}\n", std::process::id()))
.map_err(|e| format!("failed to write lock file: {}", e))?;
self.acquired = true;
Ok(true)
}
pub fn is_acquired(&self) -> bool {
self.acquired
}
pub fn release(&mut self) {
if self.acquired {
let _ = std::fs::remove_file(&self.lock_path);
self.acquired = false;
}
}
}
impl Drop for OperationLock {
fn drop(&mut self) {
self.release();
}
}
#[test]
fn test_integration_concurrent_operation_prevention() {
let dir = tempfile::tempdir().unwrap();
let lock_path = dir.path().join("rusty-stack.lock");
let mut lock1 = OperationLock::new(lock_path.clone());
let result1 = lock1.try_acquire();
assert!(result1.is_ok());
assert!(result1.unwrap(), "first lock should acquire");
assert!(lock1.is_acquired());
let mut lock2 = OperationLock::new(lock_path.clone());
let result2 = lock2.try_acquire();
assert!(result2.is_ok());
assert!(
!result2.unwrap(),
"second lock should NOT acquire (conflict)"
);
assert!(!lock2.is_acquired());
lock1.release();
let result3 = lock2.try_acquire();
assert!(result3.is_ok());
assert!(
result3.unwrap(),
"second lock should now acquire after release"
);
}
#[test]
fn test_integration_stale_lock_recovery() {
let dir = tempfile::tempdir().unwrap();
let lock_path = dir.path().join("rusty-stack.lock");
std::fs::write(&lock_path, "pid=99999\n").unwrap();
let mut lock = OperationLock::new(lock_path.clone());
let result = lock.try_acquire();
assert!(result.is_ok());
}
#[test]
fn test_integration_old_binary_parses_new_manifest_unknown_fields() {
let new_json = r#"{
"schema_version": 2,
"sequence": 100,
"generated_at": "2026-06-01T00:00:00Z",
"expires_at": null,
"min_runtime_version": "",
"components": [
{
"id": "pytorch",
"version": "2.6.0",
"script": "install_pytorch_rocm.sh",
"category": "Core",
"validation_tier": "Validated",
"min_rocm_version": "",
"compatible_channels": [],
"new_field_that_old_binary_ignores": "some value"
}
],
"signature": null,
"future_metadata": { "some": "data" }
}"#;
let result = Manifest::from_json(new_json);
assert!(
result.is_ok(),
"old binary should parse new manifest with unknown fields"
);
let manifest = result.unwrap();
assert_eq!(manifest.components.len(), 1);
assert_eq!(manifest.components[0].id, "pytorch");
}
#[test]
fn test_integration_new_binary_parses_old_manifest_missing_fields() {
let old_json = r#"{
"schema_version": 1,
"sequence": 1,
"generated_at": "",
"components": [
{
"id": "pytorch",
"version": "2.4.0",
"script": "install_pytorch_rocm.sh",
"category": "Core"
}
],
"signature": null
}"#;
let result = Manifest::from_json(old_json);
assert!(
result.is_ok(),
"new binary should parse old manifest with missing fields"
);
let manifest = result.unwrap();
assert_eq!(manifest.components.len(), 1);
assert_eq!(
manifest.components[0].validation_tier,
ValidationTier::Candidate,
"missing tier defaults to Candidate"
);
assert!(manifest.components[0].min_rocm_version.is_empty());
assert!(manifest.components[0].compatible_channels.is_empty());
}
#[test]
fn test_integration_manifest_roundtrip_preserves_data() {
let original = make_signed_manifest(
vec![
make_mc("rocm", "7.2.1", ValidationTier::Validated),
ManifestComponent {
id: "pytorch".to_string(),
version: "2.5.0".to_string(),
script: "install_pytorch_rocm.sh".to_string(),
category: Category::Core,
validation_tier: ValidationTier::Candidate,
min_rocm_version: "7.0.0".to_string(),
compatible_channels: vec!["latest".to_string(), "stable".to_string()],
dependencies: vec![],
exclusive_group: String::new(),
},
],
42,
);
let json = original.to_json().unwrap();
let restored = Manifest::from_json(&json).unwrap();
assert_eq!(original, restored, "roundtrip must preserve all data");
}
#[test]
fn test_integration_telemetry_toggle_independent_of_core_flows() {
let mut context = make_context();
context
.installed_versions
.insert("pytorch".to_string(), "2.4.0".to_string());
context.installed_components.insert("pytorch".to_string());
let manifest = make_manifest(vec![make_mc("pytorch", "2.4.1", ValidationTier::Validated)]);
let planner = UpdatePlanner::new();
let plan_off = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let executor_off = TrackingExecutor::new();
let engine_off = ApplyEngine::new(executor_off);
let summary_off = engine_off.apply(&plan_off, &ApplyOptions::default());
let verifier_off = VerifyRunner::new_noop();
let verify_off = verifier_off.verify(&summary_off);
let plan_on = planner
.build_plan(&manifest, &context, &PlannerOptions::default())
.expect("plan should succeed");
let executor_on = TrackingExecutor::new();
let engine_on = ApplyEngine::new(executor_on);
let summary_on = engine_on.apply(&plan_on, &ApplyOptions::default());
let verifier_on = VerifyRunner::new_noop();
let verify_on = verifier_on.verify(&summary_on);
assert_eq!(summary_off.success.len(), summary_on.success.len());
assert_eq!(verify_off.verified.len(), verify_on.verified.len());
for (off, on) in plan_off.iter().zip(plan_on.iter()) {
assert_eq!(off.plan_item.component_id, on.plan_item.component_id);
assert_eq!(
off.plan_item.proposed_version,
on.plan_item.proposed_version
);
assert_eq!(off.classification, on.classification);
assert_eq!(off.selected, on.selected);
}
}
#[test]
fn test_integration_telemetry_opt_in_gate_default_off() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("telemetry.json");
let gate = OptInGate::with_config_path(config_path);
assert!(!gate.is_enabled(), "telemetry must be off by default");
}
#[test]
fn test_integration_telemetry_toggle_immediate_effect() {
let dir = tempfile::tempdir().unwrap();
let config_path = dir.path().join("telemetry.json");
let mut gate = OptInGate::with_config_path(config_path);
assert!(!gate.is_enabled());
gate.enable().unwrap();
assert!(gate.is_enabled(), "enable takes effect immediately");
gate.disable().unwrap();
assert!(!gate.is_enabled(), "disable takes effect immediately");
}