use super::*;
#[test]
fn phase_bucket_json_round_trips_all_fields() {
let mut metrics = BTreeMap::new();
metrics.insert("worst_spread".to_string(), 0.42);
metrics.insert("dsq_depth_max".to_string(), 12.0);
let bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: 7,
label: "Step[6]".to_string(),
start_ms: 1500,
end_ms: u64::MAX,
sample_count: 42,
metrics,
};
let json = serde_json::to_string(&bucket).expect("serialize");
let back: PhaseBucket = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, bucket);
}
#[test]
fn phase_bucket_per_cgroup_round_trips_and_defaults_empty() {
use super::PhaseCgroupStats;
use std::collections::BTreeSet;
assert!(
PhaseBucket::default().per_cgroup.is_empty(),
"the structural carrier defaults to an empty per_cgroup map",
);
let mut bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 0,
end_ms: 1000,
sample_count: 3,
metrics: BTreeMap::new(),
};
bucket.per_cgroup.insert(
"cg_0".to_string(),
PhaseCgroupStats {
num_workers: 3,
cpus_used: BTreeSet::from([2, 5, 6]),
wake_latencies_ns: vec![10, 20, 30],
wake_sample_total: 3,
run_delays_ns: vec![1_500, 2_500],
off_cpu_pcts: vec![1.5, 11.0, 22.5],
total_migrations: 7,
total_iterations: 4200,
total_cpu_time_ns: 9_000_000,
numa_pages_local: 90,
numa_pages_total: 100,
cross_node_migrated: 4,
max_gap_ms: 13,
max_gap_cpu: 2,
stripped: false,
},
);
let json = serde_json::to_string(&bucket).expect("serialize");
let back: PhaseBucket = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, bucket);
assert_eq!(back.per_cgroup["cg_0"].total_iterations, 4200);
assert_eq!(back.per_cgroup["cg_0"].wake_latencies_ns, vec![10, 20, 30]);
assert_eq!(back.per_cgroup["cg_0"].off_cpu_pcts, vec![1.5, 11.0, 22.5]);
assert_eq!(back.per_cgroup["cg_0"].cpus_used, BTreeSet::from([2, 5, 6]),);
}
#[test]
fn phase_bucket_empty_metrics_round_trips_as_empty_object() {
let bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: 0,
label: "BASELINE".to_string(),
start_ms: 0,
end_ms: 100,
sample_count: 0,
metrics: BTreeMap::new(),
};
let json = serde_json::to_string(&bucket).expect("serialize");
assert!(
json.contains(r#""metrics":{}"#),
"empty metrics must serialize as present `metrics: {{}}`, got: {json}"
);
let back: PhaseBucket = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back, bucket);
}
#[test]
fn phase_bucket_step_index_u16_max_round_trips() {
let bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: u16::MAX,
label: "Step[65534]".to_string(),
start_ms: 0,
end_ms: 1,
sample_count: 0,
metrics: BTreeMap::new(),
};
let json = serde_json::to_string(&bucket).expect("serialize");
let back: PhaseBucket = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.step_index, u16::MAX);
assert_eq!(back, bucket);
}
#[test]
fn phase_bucket_empty_label_round_trips_as_present_field() {
let bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: 0,
label: String::new(),
start_ms: 0,
end_ms: 0,
sample_count: 0,
metrics: BTreeMap::new(),
};
let json = serde_json::to_string(&bucket).expect("serialize");
assert!(
json.contains(r#""label":"""#),
"empty label must serialize as present `label: \"\"`, got: {json}"
);
let back: PhaseBucket = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.label, "");
assert_eq!(back, bucket);
}
#[test]
fn phase_bucket_get_distinguishes_absent_from_zero() {
let mut metrics = BTreeMap::new();
metrics.insert("present".to_string(), 0.0);
let bucket = PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 0,
end_ms: 1000,
sample_count: 10,
metrics,
};
assert_eq!(bucket.get("present"), Some(0.0));
assert_eq!(bucket.get("absent"), None);
}
#[test]
fn scenario_stats_default_has_empty_phases() {
let stats = ScenarioStats::default();
assert!(stats.phases.is_empty());
assert_eq!(stats.phase(0), None);
assert_eq!(stats.phase_metric(0, "any"), None);
}
#[test]
fn scenario_stats_phase_lookup_by_step_index_not_position() {
let mut metrics_baseline = BTreeMap::new();
metrics_baseline.insert("worst_spread".to_string(), 0.10);
let mut metrics_step2 = BTreeMap::new();
metrics_step2.insert("worst_spread".to_string(), 0.42);
let stats = ScenarioStats {
phases: vec![
PhaseBucket {
per_cgroup: Default::default(),
step_index: 0,
label: "BASELINE".to_string(),
start_ms: 0,
end_ms: 100,
sample_count: 2,
metrics: metrics_baseline,
},
PhaseBucket {
per_cgroup: Default::default(),
step_index: 3,
label: "Step[2]".to_string(),
start_ms: 200,
end_ms: 300,
sample_count: 5,
metrics: metrics_step2,
},
],
..Default::default()
};
assert_eq!(stats.phase(0).map(|p| p.step_index), Some(0));
assert_eq!(stats.phase(3).map(|p| p.step_index), Some(3));
assert_eq!(stats.phase(1), None);
assert_eq!(stats.phase(2), None);
}
#[test]
fn scenario_stats_phase_metric_resolves_typed_lookup() {
let mut metrics = BTreeMap::new();
metrics.insert("worst_spread".to_string(), 0.42);
metrics.insert("dsq_depth_max".to_string(), 12.0);
let stats = ScenarioStats {
phases: vec![PhaseBucket {
per_cgroup: Default::default(),
step_index: 1,
label: "Step[0]".to_string(),
start_ms: 100,
end_ms: 200,
sample_count: 3,
metrics,
}],
..Default::default()
};
assert_eq!(stats.phase_metric(1, "worst_spread"), Some(0.42));
assert_eq!(stats.phase_metric(1, "dsq_depth_max"), Some(12.0));
assert_eq!(stats.phase_metric(1, "absent"), None);
assert_eq!(stats.phase_metric(99, "worst_spread"), None);
}
#[test]
fn scenario_stats_step_translates_scenario_step_idx_to_phase_index() {
let stats = ScenarioStats {
phases: vec![
PhaseBucket {
step_index: 0, label: "BASELINE".to_string(),
..Default::default()
},
PhaseBucket {
step_index: 1, label: "Step[0]".to_string(),
..Default::default()
},
PhaseBucket {
step_index: 2, label: "Step[1]".to_string(),
..Default::default()
},
],
..Default::default()
};
assert_eq!(stats.step(0).map(|p| p.label.as_str()), Some("Step[0]"));
assert_eq!(stats.step(1).map(|p| p.label.as_str()), Some("Step[1]"));
assert_eq!(stats.step(99), None);
assert_eq!(stats.step(u16::MAX), None);
}
#[test]
fn scenario_stats_step_metric_resolves_scenario_indexed_lookup() {
let mut metrics = BTreeMap::new();
metrics.insert("worst_spread".to_string(), 0.42);
let stats = ScenarioStats {
phases: vec![PhaseBucket {
step_index: 1, label: "Step[0]".to_string(),
metrics,
..Default::default()
}],
..Default::default()
};
assert_eq!(stats.step_metric(0, "worst_spread"), Some(0.42));
assert_eq!(stats.step_metric(0, "absent"), None);
assert_eq!(stats.step_metric(1, "worst_spread"), None);
}
#[test]
fn scenario_stats_run_metric_resolves_ext_family_sentinel_free() {
let mut ext = BTreeMap::new();
ext.insert("worst_run_delay_us".to_string(), 48.0);
ext.insert("worst_iterations_per_cpu_sec".to_string(), 12345.0);
ext.insert("worst_wake_latency_cv".to_string(), 0.0);
ext.insert("my_custom_metric".to_string(), 7.0);
let stats = ScenarioStats {
ext_metrics: ext,
worst_spread: 0.99,
..Default::default()
};
assert_eq!(stats.run_metric("worst_run_delay_us"), Some(48.0));
assert_eq!(
stats.run_metric("worst_iterations_per_cpu_sec"),
Some(12345.0)
);
assert_eq!(stats.run_metric("worst_wake_latency_cv"), Some(0.0));
assert_eq!(stats.run_metric("my_custom_metric"), Some(7.0));
assert_eq!(stats.run_metric("worst_p99_wake_latency_us"), None);
assert_eq!(stats.run_metric("totally_made_up"), None);
assert_eq!(stats.run_metric("worst_spread"), None);
assert_eq!(stats.worst_spread, 0.99);
assert_eq!(stats.run_metric("max_imbalance_ratio"), None);
}
#[test]
fn scenario_stats_is_known_metric_distinguishes_typo_from_absent_data() {
assert!(ScenarioStats::is_known_metric("worst_spread"));
assert!(!ScenarioStats::is_known_metric("worts_spread"));
assert!(!ScenarioStats::is_known_metric(""));
assert!(!ScenarioStats::is_known_metric("totally_made_up"));
}
#[test]
fn scenario_stats_known_metrics_iterates_registry() {
let names: Vec<&'static str> = ScenarioStats::known_metrics().collect();
assert!(!names.is_empty(), "METRICS registry must have entries");
assert_eq!(names.len(), crate::stats::METRICS.len());
for name in names {
assert!(
ScenarioStats::is_known_metric(name),
"every known_metrics() entry must pass is_known_metric: {name}"
);
}
}