#![allow(unused_imports)]
#![allow(clippy::field_reassign_with_default)]
use std::collections::BTreeMap;
use std::path::Path;
use super::aggregate::{format_cpu_range, merge_aggregated_into};
use super::cgroup_merge::{
merge_cgroup_cpu, merge_cgroup_memory, merge_cgroup_pids, merge_kv_counters, merge_max_option,
merge_memory_stat, merge_min_option, merge_psi,
};
use super::columns::{compare_columns_for, format_cgroup_only_section_warning};
use super::compare::sort_diff_rows_by_keys;
use super::groups::build_row;
use super::pattern::{
Segment, apply_systemd_template, cgroup_normalize_skeleton, cgroup_skeleton_tokens,
classify_token, is_token_separator, pattern_counts_union, pattern_key, split_into_segments,
tighten_group,
};
use super::render::psi_pair_has_data;
use super::scale::{auto_scale, format_delta_cell};
use super::tests_fixtures::*;
use super::*;
use crate::ctprof::{CgroupStats, CtprofSnapshot, Psi, ThreadState};
use crate::metric_types::{
Bytes, CategoricalString, CpuSet, MonotonicCount, MonotonicNs, OrdinalI32, PeakNs,
};
use regex::Regex;
#[test]
fn sum_aggregation_totals_across_group() {
let mut a = make_thread("app", "w1");
a.run_time_ns = MonotonicNs(1_000);
let mut b = make_thread("app", "w2");
b.run_time_ns = MonotonicNs(3_000);
let v = aggregate(AggRule::SumNs(|t| t.run_time_ns), &[&a, &b]);
match v {
Aggregated::Sum(s) => assert_eq!(s, 4_000),
other => panic!("expected Sum, got {other:?}"),
}
}
#[test]
fn sum_saturates_on_overflow() {
let mut a = make_thread("app", "w1");
a.run_time_ns = MonotonicNs(u64::MAX);
let mut b = make_thread("app", "w2");
b.run_time_ns = MonotonicNs(5);
let v = aggregate(AggRule::SumNs(|t| t.run_time_ns), &[&a, &b]);
match v {
Aggregated::Sum(s) => assert_eq!(s, u64::MAX),
other => panic!("expected Sum, got {other:?}"),
}
}
#[test]
fn ordinal_range_picks_extremes() {
let mut a = make_thread("app", "w1");
a.nice = OrdinalI32(-5);
let mut b = make_thread("app", "w2");
b.nice = OrdinalI32(10);
let v = aggregate(AggRule::RangeI32(|t| t.nice), &[&a, &b]);
match v {
Aggregated::OrdinalRange { min, max } => {
assert_eq!(min, -5);
assert_eq!(max, 10);
}
other => panic!("expected OrdinalRange, got {other:?}"),
}
}
#[test]
fn mode_aggregation_picks_most_frequent() {
let mut a = make_thread("app", "w1");
a.policy = "SCHED_OTHER".into();
let mut b = make_thread("app", "w2");
b.policy = "SCHED_OTHER".into();
let mut c = make_thread("app", "w3");
c.policy = "SCHED_FIFO".into();
let v = aggregate(AggRule::Mode(|t| t.policy.clone()), &[&a, &b, &c]);
match v {
Aggregated::Mode { ref tallies, total } => {
assert_eq!(v.mode_value(), "SCHED_OTHER");
assert_eq!(v.mode_count(), 2);
assert_eq!(total, 3);
assert_eq!(tallies.get("SCHED_OTHER").copied(), Some(2));
assert_eq!(tallies.get("SCHED_FIFO").copied(), Some(1));
}
other => panic!("expected Mode, got {other:?}"),
}
}
#[test]
fn affinity_uniform_preserves_cpuset() {
let a = make_thread("app", "w1");
let b = make_thread("app", "w2");
let v = aggregate(AggRule::Affinity(|t| t.cpu_affinity.clone()), &[&a, &b]);
match v {
Aggregated::Affinity(s) => {
assert_eq!(s.min_cpus, 4);
assert_eq!(s.max_cpus, 4);
assert_eq!(s.uniform, Some(vec![0, 1, 2, 3]));
}
other => panic!("expected Affinity, got {other:?}"),
}
}
#[test]
fn affinity_heterogeneous_drops_uniform() {
let a = make_thread("app", "w1");
let mut b = make_thread("app", "w2");
b.cpu_affinity = CpuSet(vec![4, 5]);
let v = aggregate(AggRule::Affinity(|t| t.cpu_affinity.clone()), &[&a, &b]);
match v {
Aggregated::Affinity(s) => {
assert_eq!(s.min_cpus, 2);
assert_eq!(s.max_cpus, 4);
assert!(s.uniform.is_none());
}
other => panic!("expected Affinity, got {other:?}"),
}
}
#[test]
fn format_cpu_range_collapses_contiguous_runs() {
assert_eq!(format_cpu_range(&[0, 1, 2, 3]), "0-3");
assert_eq!(format_cpu_range(&[0, 1, 4, 5, 7]), "0-1,4-5,7");
assert_eq!(format_cpu_range(&[3]), "3");
assert_eq!(format_cpu_range(&[]), "");
}
#[test]
fn ordinal_display_collapses_degenerate_range() {
let r = Aggregated::OrdinalRange { min: 0, max: 0 };
assert_eq!(r.to_string(), "0");
let r = Aggregated::OrdinalRange { min: -5, max: 10 };
assert_eq!(r.to_string(), "-5..10");
}
#[test]
fn mode_display_hides_ratio_when_unanimous() {
let m = Aggregated::mode_single("SCHED_OTHER".into(), 4, 4);
assert_eq!(m.to_string(), "SCHED_OTHER");
let m = Aggregated::mode_single("SCHED_OTHER".into(), 3, 5);
assert_eq!(m.to_string(), "SCHED_OTHER (3/5)");
}
#[test]
fn aggregate_ordinal_range_on_empty_threads_is_zero() {
let empty: Vec<&ThreadState> = vec![];
let v = aggregate(AggRule::RangeI32(|t| t.nice), &empty);
match v {
Aggregated::OrdinalRange { min, max } => {
assert_eq!(min, 0);
assert_eq!(max, 0);
}
other => panic!("expected OrdinalRange, got {other:?}"),
}
}
#[test]
fn aggregated_max_numeric_and_display() {
let m = Aggregated::Max(7_500_000);
assert_eq!(m.numeric(), Some(7_500_000.0));
assert_eq!(format!("{m}"), "7500000");
}
#[test]
fn numeric_returns_none_for_mode() {
let m = Aggregated::mode_single("SCHED_OTHER".into(), 4, 4);
assert!(m.numeric().is_none());
}
#[test]
fn numeric_returns_midpoint_for_affinity_heterogeneous() {
let a = Aggregated::Affinity(AffinitySummary {
min_cpus: 2,
max_cpus: 8,
uniform: None,
});
assert_eq!(a.numeric(), Some(5.0));
let b = Aggregated::Affinity(AffinitySummary {
min_cpus: 4,
max_cpus: 4,
uniform: None,
});
assert_eq!(b.numeric(), Some(4.0));
}
#[test]
fn affinity_display_uniform_noncontiguous_renders_comma_separated() {
let a = Aggregated::Affinity(AffinitySummary {
min_cpus: 2,
max_cpus: 2,
uniform: Some(vec![0, 2]),
});
assert_eq!(a.to_string(), "2 cpus (0,2)");
}
#[test]
fn affinity_display_heterogeneous_same_count_renders_mixed() {
let a = Aggregated::Affinity(AffinitySummary {
min_cpus: 3,
max_cpus: 3,
uniform: None,
});
assert_eq!(a.to_string(), "3 cpus (mixed)");
}
#[test]
fn fudge_n_to_one_merge_unions_ordinal_range() {
let threads_a = fudge_threads_with("/svc", 10, |t| {
t.nice = OrdinalI32(0);
});
let mut threads_b = fudge_threads_with("/svc-a", 10, |t| {
t.nice = OrdinalI32(-5);
});
threads_b.extend(fudge_threads_with("/svc-b", 10, |t| {
t.nice = OrdinalI32(5);
}));
let diff = fudge_compare(&snap_with(threads_a), &snap_with(threads_b));
let r = diff
.rows
.iter()
.find(|r| r.metric_name == "nice" && r.group_key.contains("alpha"))
.expect("nice alpha row must surface");
match &r.candidate {
Aggregated::OrdinalRange { min, max } => {
assert_eq!(*min, -5, "merged candidate nice range min must be -5");
assert_eq!(*max, 5, "merged candidate nice range max must be 5");
}
other => panic!("expected OrdinalRange for nice; got {other:?}"),
}
}