#![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 parse_sort_by_unknown_lists_derived_names() {
let err = parse_sort_by("not_a_real_metric").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("affine_success_ratio")
|| msg.contains("cpu_efficiency")
|| msg.contains("avg_wait_ns"),
"error must list derived metric names alongside primary; got: {msg}",
);
}
#[test]
fn new_constrained_table_constraints_survive_header_replacement() {
let display = DisplayOptions::default();
let max_widths: Vec<u16> = vec![5];
let mut t = display.new_constrained_table(&max_widths);
t.set_header(vec!["col"]);
t.add_row(vec!["aaaaaaaaaaaaaaaaaaaa"]);
let rendered = t.to_string();
for line in rendered.lines() {
assert!(
line.chars().count() <= 16,
"rendered line exceeds constrained width of 5; constraints \
may not survive set_header replacement: \n{rendered}"
);
}
}
#[test]
fn parse_sort_by_empty_returns_empty_vec() {
let keys = parse_sort_by("").expect("empty parses");
assert!(keys.is_empty());
}
#[test]
fn parse_sort_by_single_field_defaults_to_desc() {
let keys = parse_sort_by("wait_sum").expect("parse");
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].metric, "wait_sum");
assert!(keys[0].descending);
}
#[test]
fn parse_sort_by_explicit_directions() {
let keys = parse_sort_by("wait_sum:asc,run_time_ns:desc").expect("parse");
assert_eq!(keys.len(), 2);
assert_eq!(keys[0].metric, "wait_sum");
assert!(!keys[0].descending);
assert_eq!(keys[1].metric, "run_time_ns");
assert!(keys[1].descending);
}
#[test]
fn parse_sort_by_trims_whitespace_between_entries() {
let keys = parse_sort_by(" wait_sum:desc , run_time_ns:asc ").expect("parse");
assert_eq!(keys.len(), 2);
assert_eq!(keys[0].metric, "wait_sum");
assert!(keys[0].descending);
assert_eq!(keys[1].metric, "run_time_ns");
assert!(!keys[1].descending);
}
#[test]
fn parse_sort_by_trims_whitespace_around_colon() {
let keys = parse_sort_by("wait_sum : desc").expect("trimmed colon parse");
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].metric, "wait_sum");
assert!(keys[0].descending);
let keys2 = parse_sort_by("run_time_ns: asc ").expect("trimmed asc-side parse");
assert_eq!(keys2.len(), 1);
assert_eq!(keys2[0].metric, "run_time_ns");
assert!(!keys2[0].descending);
}
#[test]
fn parse_sort_by_direction_is_case_insensitive() {
for spec in ["wait_sum:DESC", "wait_sum:Desc", "wait_sum:dEsC"] {
let keys = parse_sort_by(spec).unwrap_or_else(|e| panic!("{spec} must parse: {e}"));
assert_eq!(keys.len(), 1, "{spec}");
assert!(keys[0].descending, "{spec}");
}
for spec in ["wait_sum:ASC", "wait_sum:Asc", "wait_sum:aSc"] {
let keys = parse_sort_by(spec).unwrap_or_else(|e| panic!("{spec} must parse: {e}"));
assert_eq!(keys.len(), 1, "{spec}");
assert!(!keys[0].descending, "{spec}");
}
}
#[test]
fn parse_sort_by_rejects_unknown_metric() {
let err = parse_sort_by("not_a_real_metric").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("not_a_real_metric"),
"error must cite offending metric name, got: {msg}"
);
assert!(
msg.contains("must be one of"),
"error must include the 'must be one of' preamble that introduces the valid-name list, got: {msg}"
);
assert!(
msg.contains("run_time_ns"),
"error must list at least one canonical metric name from the registry, got: {msg}"
);
assert!(
msg.contains("bare metric name"),
"error must hint at bare-metric-name usage, got: {msg}"
);
}
#[test]
fn parse_sort_by_unknown_with_tag_suffix_carries_hint() {
let err = parse_sort_by("wait_sum [non-ext] [SCHEDSTATS]").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("bare metric name"),
"tagged-cell paste must produce the bare-name hint, got: {msg}",
);
}
#[test]
fn parse_sort_by_rejects_invalid_direction() {
let err = parse_sort_by("wait_sum:sideways").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("sideways"),
"error must cite offending direction, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_empty_entry() {
let err = parse_sort_by("wait_sum,,run_time_ns").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("empty entry"),
"error must mention empty entry, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_trailing_comma() {
let err = parse_sort_by("wait_sum,").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("empty entry"),
"trailing comma must surface as empty-entry error, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_leading_comma() {
let err = parse_sort_by(",wait_sum").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("empty entry"),
"leading comma must surface as empty-entry error, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_bare_colon() {
let err = parse_sort_by(":").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("invalid direction"),
"bare colon must surface as invalid-direction error, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_categorical_metric() {
let policy_def = CTPROF_METRICS
.iter()
.find(|m| m.name == "policy")
.expect("policy must be in CTPROF_METRICS");
assert!(
matches!(policy_def.rule, AggRule::Mode(_)),
"test premise drift: policy is no longer Mode-aggregated; \
pick a different categorical metric for this test",
);
let err = parse_sort_by("policy").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("categorical"),
"categorical metric error must label the failure mode, got: {msg}"
);
assert!(
msg.contains("policy"),
"categorical metric error must name the offending metric, got: {msg}"
);
}
#[test]
fn parse_sort_by_rejects_duplicate_metric() {
let err = parse_sort_by("wait_sum,wait_sum").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("duplicate"),
"duplicate-metric error must label the failure mode, got: {msg}"
);
assert!(
msg.contains("wait_sum"),
"duplicate-metric error must name the offending metric, got: {msg}"
);
let err2 = parse_sort_by("wait_sum:asc,wait_sum:desc").unwrap_err();
let msg2 = format!("{err2:#}");
assert!(
msg2.contains("duplicate"),
"duplicate metric across different directions must still reject, got: {msg2}"
);
}
#[test]
fn parse_sort_by_multi_key_preserves_order() {
let keys = parse_sort_by("run_time_ns:desc,nr_wakeups:asc,wait_time_ns:desc").expect("parse");
assert_eq!(keys.len(), 3);
assert_eq!(keys[0].metric, "run_time_ns");
assert!(keys[0].descending);
assert_eq!(keys[1].metric, "nr_wakeups");
assert!(!keys[1].descending);
assert_eq!(keys[2].metric, "wait_time_ns");
assert!(keys[2].descending);
}
#[test]
fn is_metric_enabled_empty_treats_all_as_on() {
let opts = DisplayOptions::default();
assert!(opts.is_metric_enabled("run_time_ns"));
assert!(opts.is_metric_enabled("cpu_efficiency"));
assert!(opts.is_metric_enabled("anything_under_empty_filter"));
}
#[test]
fn is_metric_enabled_non_empty_restricts_to_listed() {
let mut opts = DisplayOptions::default();
opts.metrics = vec!["run_time_ns", "wait_sum"];
assert!(opts.is_metric_enabled("run_time_ns"));
assert!(opts.is_metric_enabled("wait_sum"));
assert!(!opts.is_metric_enabled("nr_wakeups"));
assert!(!opts.is_metric_enabled("cpu_efficiency"));
}
#[test]
fn parse_sort_by_accepts_derived_metric_name() {
let keys = parse_sort_by("cpu_efficiency").expect("derived name parses");
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].metric, "cpu_efficiency");
assert!(keys[0].descending);
}
#[test]
fn parse_sort_by_bare_metric_with_whitespace_no_colon() {
let keys = parse_sort_by(" wait_sum ").expect("bare-metric whitespace must parse");
assert_eq!(keys.len(), 1);
assert_eq!(keys[0].metric, "wait_sum");
assert!(keys[0].descending);
}
#[test]
fn parse_sort_by_rejects_metric_colon_no_direction() {
let err = parse_sort_by("wait_sum:").unwrap_err();
let msg = format!("{err:#}");
assert!(
msg.contains("invalid direction"),
"metric-colon-no-direction must surface as invalid-direction error, got: {msg}"
);
}
#[test]
fn parse_sort_by_unknown_metric_lists_valid_names_sorted() {
let err = parse_sort_by("not_a_real_metric").unwrap_err();
let msg = format!("{err:#}");
let nice_at = msg
.find("nice")
.expect("error must list 'nice' from the registry");
let policy_at = msg
.find("policy")
.expect("error must list 'policy' from the registry");
let run_time_at = msg
.find("run_time_ns")
.expect("error must list 'run_time_ns' from the registry");
assert!(
nice_at < policy_at,
"names must appear in alphabetical order: \
nice@{nice_at} < policy@{policy_at}\nmsg: {msg}",
);
assert!(
policy_at < run_time_at,
"names must appear in alphabetical order: \
policy@{policy_at} < run_time_ns@{run_time_at}\nmsg: {msg}",
);
assert!(
!msg.contains("{\""),
"error must use comma-separated list, not BTreeSet debug dump:\n{msg}"
);
}