use super::tests_common::rpt;
use super::*;
#[test]
fn assert_no_overrides_has_no_checks() {
let v = Assert::NO_OVERRIDES;
assert!(v.not_starved.is_none());
assert!(v.isolation.is_none());
assert!(v.max_gap_ms.is_none());
assert!(v.max_spread_pct.is_none());
assert!(v.max_imbalance_ratio.is_none());
}
#[test]
fn assert_default_checks_is_no_overrides() {
let v = Assert::default_checks();
assert!(v.not_starved.is_none());
assert!(v.isolation.is_none());
assert!(v.max_imbalance_ratio.is_none());
assert!(v.max_local_dsq_depth.is_none());
assert!(v.fail_on_stall.is_none());
assert!(v.sustained_samples.is_none());
assert!(v.max_fallback_rate.is_none());
assert!(v.max_keep_last_rate.is_none());
}
#[test]
fn with_monitor_defaults_propagates_enforce_true_to_thresholds() {
let assert = Assert::NO_OVERRIDES.with_monitor_defaults();
let t = assert.monitor_thresholds();
assert!(
t.enforce,
"with_monitor_defaults() must propagate enforce=true to MonitorThresholds"
);
}
#[test]
fn assert_default_without_monitor_defaults_yields_enforce_false() {
let assert = Assert::NO_OVERRIDES;
let t = assert.monitor_thresholds();
assert!(
!t.enforce,
"default Assert (no .with_monitor_defaults()) must yield enforce=false"
);
}
#[test]
fn no_overrides_enforce_monitor_thresholds_field_is_false() {
let v = Assert::NO_OVERRIDES;
assert!(
!v.enforce_monitor_thresholds,
"NO_OVERRIDES.enforce_monitor_thresholds must be false (post-enforce-default-flip default)"
);
}
#[test]
fn merge_enforce_monitor_thresholds_or_semantics_false_false() {
let merged = Assert::NO_OVERRIDES.merge(&Assert::NO_OVERRIDES);
assert!(
!merged.enforce_monitor_thresholds,
"false || false must yield false"
);
}
#[test]
fn merge_enforce_monitor_thresholds_or_semantics_true_left() {
let left = Assert::NO_OVERRIDES.with_monitor_defaults();
let right = Assert::NO_OVERRIDES;
let merged = left.merge(&right);
assert!(
merged.enforce_monitor_thresholds,
"true (self) || false (other) must yield true (sticky OR)"
);
}
#[test]
fn merge_enforce_monitor_thresholds_or_semantics_false_true() {
let left = Assert::NO_OVERRIDES;
let right = Assert::NO_OVERRIDES.with_monitor_defaults();
let merged = left.merge(&right);
assert!(
merged.enforce_monitor_thresholds,
"false (self) || true (other) must yield true (sticky OR)"
);
}
#[test]
fn with_monitor_defaults_fills_all_unset_threshold_fields() {
use crate::monitor::MonitorThresholds;
let assert = Assert::NO_OVERRIDES.with_monitor_defaults();
let d = MonitorThresholds::new();
assert!(
(assert.max_imbalance_ratio.unwrap() - d.max_imbalance_ratio).abs() < f64::EPSILON,
"max_imbalance_ratio must be auto-filled with DEFAULT"
);
assert_eq!(
assert.max_local_dsq_depth,
Some(d.max_local_dsq_depth),
"max_local_dsq_depth must be auto-filled with DEFAULT"
);
assert_eq!(
assert.fail_on_stall,
Some(d.fail_on_stall),
"fail_on_stall must be auto-filled with DEFAULT"
);
assert_eq!(
assert.sustained_samples,
Some(d.sustained_samples),
"sustained_samples must be auto-filled with DEFAULT"
);
assert!(
(assert.max_fallback_rate.unwrap() - d.max_fallback_rate).abs() < f64::EPSILON,
"max_fallback_rate must be auto-filled with DEFAULT"
);
assert!(
(assert.max_keep_last_rate.unwrap() - d.max_keep_last_rate).abs() < f64::EPSILON,
"max_keep_last_rate must be auto-filled with DEFAULT"
);
assert!(
assert.enforce_monitor_thresholds,
"enforce_monitor_thresholds must be set to true"
);
}
#[test]
fn merge_enforce_monitor_thresholds_or_semantics_true_true() {
let left = Assert::NO_OVERRIDES.with_monitor_defaults();
let right = Assert::NO_OVERRIDES.with_monitor_defaults();
let merged = left.merge(&right);
assert!(
merged.enforce_monitor_thresholds,
"true || true must yield true (OR, not XOR)"
);
}
#[test]
fn with_monitor_defaults_preserves_user_set_values() {
use crate::monitor::MonitorThresholds;
let d = MonitorThresholds::new();
let custom_imbalance = d.max_imbalance_ratio * 0.5;
let custom_local_dsq = d.max_local_dsq_depth * 2;
let custom_fail_on_stall = !d.fail_on_stall;
let custom_sustained = d.sustained_samples * 2;
let custom_fallback = d.max_fallback_rate * 0.5;
let custom_keep_last = d.max_keep_last_rate * 0.5;
assert!(
(custom_imbalance - d.max_imbalance_ratio).abs() > f64::EPSILON,
"test derivation produced custom_imbalance == DEFAULT — DEFAULT.max_imbalance_ratio may have shifted to 0"
);
assert_ne!(
custom_local_dsq, d.max_local_dsq_depth,
"test derivation produced custom_local_dsq == DEFAULT — DEFAULT.max_local_dsq_depth may have shifted to 0"
);
assert_ne!(
custom_fail_on_stall, d.fail_on_stall,
"bool flip should always differ"
);
assert_ne!(
custom_sustained, d.sustained_samples,
"test derivation produced custom_sustained == DEFAULT — DEFAULT.sustained_samples may have shifted to 0"
);
assert!(
(custom_fallback - d.max_fallback_rate).abs() > f64::EPSILON,
"test derivation produced custom_fallback == DEFAULT — DEFAULT.max_fallback_rate may have shifted to 0"
);
assert!(
(custom_keep_last - d.max_keep_last_rate).abs() > f64::EPSILON,
"test derivation produced custom_keep_last == DEFAULT — DEFAULT.max_keep_last_rate may have shifted to 0"
);
let assert = Assert::NO_OVERRIDES
.max_imbalance_ratio(custom_imbalance)
.max_local_dsq_depth(custom_local_dsq)
.fail_on_stall(custom_fail_on_stall)
.sustained_samples(custom_sustained)
.max_fallback_rate(custom_fallback)
.max_keep_last_rate(custom_keep_last)
.with_monitor_defaults();
assert!(
(assert.max_imbalance_ratio.unwrap() - custom_imbalance).abs() < f64::EPSILON,
"max_imbalance_ratio user-set value must survive with_monitor_defaults"
);
assert_eq!(
assert.max_local_dsq_depth,
Some(custom_local_dsq),
"max_local_dsq_depth user-set value must survive"
);
assert_eq!(
assert.fail_on_stall,
Some(custom_fail_on_stall),
"fail_on_stall user-set value must survive"
);
assert_eq!(
assert.sustained_samples,
Some(custom_sustained),
"sustained_samples user-set value must survive"
);
assert!(
(assert.max_fallback_rate.unwrap() - custom_fallback).abs() < f64::EPSILON,
"max_fallback_rate user-set value must survive"
);
assert!(
(assert.max_keep_last_rate.unwrap() - custom_keep_last).abs() < f64::EPSILON,
"max_keep_last_rate user-set value must survive"
);
assert!(
assert.enforce_monitor_thresholds,
"enforce_monitor_thresholds still set to true (the only field with_monitor_defaults unconditionally writes)"
);
}
#[test]
fn has_monitor_thresholds_false_when_all_none() {
let v = Assert::NO_OVERRIDES;
assert!(
!v.has_monitor_thresholds(),
"NO_OVERRIDES must report has_monitor_thresholds() == false"
);
}
#[test]
fn has_monitor_thresholds_true_when_any_set() {
use crate::monitor::MonitorThresholds;
let _ = MonitorThresholds {
max_imbalance_ratio: 0.0,
max_local_dsq_depth: 0,
fail_on_stall: false,
sustained_samples: 0,
max_fallback_rate: 0.0,
max_keep_last_rate: 0.0,
enforce: false,
};
type FieldSetter = (&'static str, fn() -> Assert);
let setters: &[FieldSetter] = &[
("max_imbalance_ratio", || {
Assert::NO_OVERRIDES.max_imbalance_ratio(3.0)
}),
("max_local_dsq_depth", || {
Assert::NO_OVERRIDES.max_local_dsq_depth(64)
}),
("fail_on_stall", || Assert::NO_OVERRIDES.fail_on_stall(true)),
("sustained_samples", || {
Assert::NO_OVERRIDES.sustained_samples(7)
}),
("max_fallback_rate", || {
Assert::NO_OVERRIDES.max_fallback_rate(150.0)
}),
("max_keep_last_rate", || {
Assert::NO_OVERRIDES.max_keep_last_rate(75.0)
}),
];
for (field, build) in setters {
let v = build();
assert!(
v.has_monitor_thresholds(),
"has_monitor_thresholds() must return true when only `{field}` is set"
);
}
}
#[test]
fn assert_merge_other_overrides_self() {
let base = Assert::NO_OVERRIDES;
let other = Assert::NO_OVERRIDES
.check_not_starved()
.max_gap_ms(5000)
.max_imbalance_ratio(2.0);
let merged = base.merge(&other);
assert_eq!(merged.not_starved, Some(true));
assert_eq!(merged.max_gap_ms, Some(5000));
assert_eq!(merged.max_imbalance_ratio, Some(2.0));
}
#[test]
fn assert_merge_preserves_self_when_other_is_none() {
let base = Assert::NO_OVERRIDES
.check_not_starved()
.max_imbalance_ratio(4.0);
let merged = base.merge(&Assert::NO_OVERRIDES);
assert_eq!(merged.not_starved, Some(true));
assert_eq!(merged.max_imbalance_ratio, Some(4.0));
}
#[test]
fn assert_merge_other_takes_precedence() {
let base = Assert::NO_OVERRIDES.max_imbalance_ratio(4.0);
let other = Assert::NO_OVERRIDES.max_imbalance_ratio(2.0);
let merged = base.merge(&other);
assert_eq!(merged.max_imbalance_ratio, Some(2.0));
}
#[test]
fn assert_merge_last_some_wins() {
let base = Assert::NO_OVERRIDES.check_not_starved();
let other = Assert::NO_OVERRIDES.check_isolation();
let merged = base.merge(&other);
assert_eq!(merged.not_starved, Some(true));
assert_eq!(merged.isolation, Some(true));
}
#[test]
fn assert_merge_child_disables_not_starved() {
let base = Assert::NO_OVERRIDES.check_not_starved();
let other = Assert {
not_starved: Some(false),
..Assert::NO_OVERRIDES
};
let merged = base.merge(&other);
assert_eq!(merged.not_starved, Some(false));
assert!(!merged.worker_plan().not_starved);
}
#[test]
fn assert_merge_child_disables_isolation() {
let base = Assert::NO_OVERRIDES.check_isolation(); let other = Assert {
isolation: Some(false),
..Assert::NO_OVERRIDES
};
let merged = base.merge(&other);
assert_eq!(merged.isolation, Some(false));
assert!(!merged.worker_plan().isolation);
}
#[test]
fn assert_worker_plan_extraction() {
let v = Assert::NO_OVERRIDES
.check_not_starved()
.check_isolation()
.max_gap_ms(3000)
.max_spread_pct(25.0);
assert_eq!(v.not_starved, Some(true));
assert_eq!(v.isolation, Some(true));
let plan = v.worker_plan();
assert!(plan.not_starved);
assert!(plan.isolation);
assert_eq!(plan.max_gap_ms, Some(3000));
assert_eq!(plan.max_spread_pct, Some(25.0));
}
#[test]
fn assert_cgroup_delegates_to_plan() {
let v = Assert::NO_OVERRIDES.check_not_starved();
let reports = [rpt(1, 1000, 5e9 as u64, 5e8 as u64, &[0], 50)];
let r = v.assert_cgroup(&reports, None);
assert!(r.is_pass());
assert_eq!(r.stats.total_workers, 1);
}
#[test]
fn assert_monitor_thresholds_extraction() {
let v = Assert::NO_OVERRIDES
.max_imbalance_ratio(2.5)
.max_local_dsq_depth(100)
.fail_on_stall(false)
.sustained_samples(10)
.max_fallback_rate(50.0)
.max_keep_last_rate(25.0);
let t = v.monitor_thresholds();
assert!((t.max_imbalance_ratio - 2.5).abs() < f64::EPSILON);
assert_eq!(t.max_local_dsq_depth, 100);
assert!(!t.fail_on_stall);
assert_eq!(t.sustained_samples, 10);
assert!((t.max_fallback_rate - 50.0).abs() < f64::EPSILON);
assert!((t.max_keep_last_rate - 25.0).abs() < f64::EPSILON);
}
#[test]
fn assert_monitor_thresholds_defaults_when_none() {
let v = Assert::NO_OVERRIDES;
let t = v.monitor_thresholds();
let d = crate::monitor::MonitorThresholds::new();
assert!((t.max_imbalance_ratio - d.max_imbalance_ratio).abs() < f64::EPSILON);
assert_eq!(t.max_local_dsq_depth, d.max_local_dsq_depth);
}
#[test]
fn assert_chain_all_setters() {
let v = Assert::NO_OVERRIDES
.check_not_starved()
.check_isolation()
.max_gap_ms(1000)
.max_spread_pct(5.0)
.max_imbalance_ratio(3.0)
.max_local_dsq_depth(20)
.fail_on_stall(true)
.sustained_samples(3)
.max_fallback_rate(100.0)
.max_keep_last_rate(50.0);
assert_eq!(v.not_starved, Some(true));
assert_eq!(v.isolation, Some(true));
assert_eq!(v.max_gap_ms, Some(1000));
assert_eq!(v.max_spread_pct, Some(5.0));
assert_eq!(v.max_imbalance_ratio, Some(3.0));
assert_eq!(v.max_local_dsq_depth, Some(20));
assert_eq!(v.fail_on_stall, Some(true));
assert_eq!(v.sustained_samples, Some(3));
assert_eq!(v.max_fallback_rate, Some(100.0));
assert_eq!(v.max_keep_last_rate, Some(50.0));
}
#[test]
fn gap_threshold_default() {
let t = gap_threshold_ms();
if cfg!(debug_assertions) {
assert_eq!(t, 3000);
} else {
assert_eq!(t, 2000);
}
}
#[test]
fn assert_merge_max_spread_pct() {
let base = Assert::NO_OVERRIDES.max_spread_pct(10.0);
let other = Assert::NO_OVERRIDES.max_spread_pct(5.0);
assert_eq!(base.merge(&other).max_spread_pct, Some(5.0));
assert_eq!(base.merge(&Assert::NO_OVERRIDES).max_spread_pct, Some(10.0));
}
#[test]
fn assert_merge_fail_on_stall() {
let base = Assert::NO_OVERRIDES.fail_on_stall(true);
let other = Assert::NO_OVERRIDES.fail_on_stall(false);
assert_eq!(base.merge(&other).fail_on_stall, Some(false));
assert_eq!(base.merge(&Assert::NO_OVERRIDES).fail_on_stall, Some(true));
}
#[test]
fn assert_merge_sustained_samples() {
let base = Assert::NO_OVERRIDES.sustained_samples(5);
let other = Assert::NO_OVERRIDES.sustained_samples(10);
assert_eq!(base.merge(&other).sustained_samples, Some(10));
assert_eq!(base.merge(&Assert::NO_OVERRIDES).sustained_samples, Some(5));
}
#[test]
fn assert_merge_max_fallback_rate() {
let base = Assert::NO_OVERRIDES.max_fallback_rate(200.0);
let other = Assert::NO_OVERRIDES.max_fallback_rate(50.0);
assert_eq!(base.merge(&other).max_fallback_rate, Some(50.0));
assert_eq!(
base.merge(&Assert::NO_OVERRIDES).max_fallback_rate,
Some(200.0)
);
}
#[test]
fn assert_merge_max_keep_last_rate() {
let base = Assert::NO_OVERRIDES.max_keep_last_rate(100.0);
let other = Assert::NO_OVERRIDES.max_keep_last_rate(25.0);
assert_eq!(base.merge(&other).max_keep_last_rate, Some(25.0));
assert_eq!(
base.merge(&Assert::NO_OVERRIDES).max_keep_last_rate,
Some(100.0)
);
}
#[test]
fn assert_merge_max_local_dsq_depth() {
let base = Assert::NO_OVERRIDES.max_local_dsq_depth(50);
let other = Assert::NO_OVERRIDES.max_local_dsq_depth(100);
assert_eq!(base.merge(&other).max_local_dsq_depth, Some(100));
assert_eq!(
base.merge(&Assert::NO_OVERRIDES).max_local_dsq_depth,
Some(50)
);
}
#[test]
fn assert_merge_max_gap_ms() {
let base = Assert::NO_OVERRIDES.max_gap_ms(2000);
let other = Assert::NO_OVERRIDES.max_gap_ms(5000);
assert_eq!(base.merge(&other).max_gap_ms, Some(5000));
assert_eq!(base.merge(&Assert::NO_OVERRIDES).max_gap_ms, Some(2000));
}
#[test]
fn assert_merge_three_layers() {
let defaults = Assert::default_checks();
let sched = Assert::NO_OVERRIDES
.max_imbalance_ratio(2.0)
.max_fallback_rate(50.0);
let test = Assert::NO_OVERRIDES.max_gap_ms(5000);
let merged = defaults.merge(&sched).merge(&test);
assert_eq!(merged.not_starved, None);
assert_eq!(merged.max_imbalance_ratio, Some(2.0));
assert_eq!(merged.max_fallback_rate, Some(50.0));
assert_eq!(merged.max_gap_ms, Some(5000));
assert_eq!(merged.sustained_samples, None);
}
#[test]
fn assert_merge_no_overrides_preserves_base() {
let base = Assert::NO_OVERRIDES
.check_not_starved()
.max_imbalance_ratio(4.0)
.fail_on_stall(true);
let merged = base.merge(&Assert::NO_OVERRIDES);
assert_eq!(merged.not_starved, Some(true));
assert_eq!(merged.max_imbalance_ratio, Some(4.0));
assert_eq!(merged.fail_on_stall, Some(true));
}
#[test]
fn assert_merge_no_overrides_is_left_identity() {
let merged = Assert::NO_OVERRIDES.merge(&Assert::default_checks());
assert!(merged.not_starved.is_none());
assert!(merged.max_imbalance_ratio.is_none());
assert!(merged.max_gap_ms.is_none());
assert!(merged.isolation.is_none());
}
#[test]
fn assert_merge_runtime_chain_with_no_overrides_yields_defaults() {
let scheduler_assert = Assert::NO_OVERRIDES;
let test_assert = Assert::NO_OVERRIDES;
let merged = Assert::default_checks()
.merge(&scheduler_assert)
.merge(&test_assert);
assert!(merged.not_starved.is_none());
assert!(merged.max_imbalance_ratio.is_none());
assert!(merged.max_local_dsq_depth.is_none());
assert!(merged.fail_on_stall.is_none());
}
#[test]
fn assert_merge_overrides_fields() {
let base = Assert::NO_OVERRIDES;
let overrides = Assert::NO_OVERRIDES
.max_imbalance_ratio(5.0)
.max_gap_ms(1000)
.check_not_starved();
let merged = base.merge(&overrides);
assert_eq!(merged.not_starved, Some(true));
assert_eq!(merged.max_imbalance_ratio, Some(5.0));
assert_eq!(merged.max_gap_ms, Some(1000));
}
#[test]
fn assert_merge_later_overrides_earlier() {
let a = Assert::NO_OVERRIDES.max_imbalance_ratio(2.0);
let b = Assert::NO_OVERRIDES.max_imbalance_ratio(10.0);
let merged = a.merge(&b);
assert_eq!(merged.max_imbalance_ratio, Some(10.0));
}
#[test]
fn assert_worker_plan_extracts_fields() {
let v = Assert::NO_OVERRIDES
.check_not_starved()
.check_isolation()
.max_gap_ms(500)
.max_spread_pct(10.0);
assert_eq!(v.not_starved, Some(true));
assert_eq!(v.isolation, Some(true));
let plan = v.worker_plan();
assert!(plan.not_starved);
assert!(plan.isolation);
assert_eq!(plan.max_gap_ms, Some(500));
assert_eq!(plan.max_spread_pct, Some(10.0));
}
#[test]
fn assert_monitor_thresholds_defaults() {
let v = Assert::NO_OVERRIDES;
let t = v.monitor_thresholds();
let d = crate::monitor::MonitorThresholds::new();
assert_eq!(t.max_imbalance_ratio, d.max_imbalance_ratio);
assert_eq!(t.max_local_dsq_depth, d.max_local_dsq_depth);
}
#[test]
fn assert_monitor_thresholds_overridden() {
let v = Assert::NO_OVERRIDES
.max_imbalance_ratio(99.0)
.max_local_dsq_depth(42)
.fail_on_stall(false)
.sustained_samples(10)
.max_fallback_rate(0.5)
.max_keep_last_rate(0.3);
let t = v.monitor_thresholds();
assert_eq!(t.max_imbalance_ratio, 99.0);
assert_eq!(t.max_local_dsq_depth, 42);
assert!(!t.fail_on_stall);
assert_eq!(t.sustained_samples, 10);
assert_eq!(t.max_fallback_rate, 0.5);
assert_eq!(t.max_keep_last_rate, 0.3);
}
#[test]
fn assert_max_spread_pct() {
let v = Assert::NO_OVERRIDES.max_spread_pct(25.0);
assert_eq!(v.max_spread_pct, Some(25.0));
}
#[test]
fn gap_threshold_debug_vs_release() {
let t = gap_threshold_ms();
assert!(t >= 2000, "threshold should be at least 2000ms: {t}");
}
#[test]
fn assert_no_overrides_has_no_worker_checks() {
assert!(!Assert::NO_OVERRIDES.has_worker_checks());
}
#[test]
fn assert_default_checks_has_no_worker_checks() {
assert!(!Assert::default_checks().has_worker_checks());
}
#[test]
fn assert_single_field_has_worker_checks() {
assert!(Assert::NO_OVERRIDES.max_gap_ms(5000).has_worker_checks());
assert!(Assert::NO_OVERRIDES.check_isolation().has_worker_checks());
assert!(
Assert::NO_OVERRIDES
.max_spread_pct(10.0)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.max_throughput_cv(0.5)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.min_work_rate(100.0)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.max_p99_wake_latency_ns(1000)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.max_wake_latency_cv(0.5)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.min_iteration_rate(10.0)
.has_worker_checks()
);
assert!(
Assert::NO_OVERRIDES
.max_migration_ratio(0.5)
.has_worker_checks()
);
}
#[test]
fn assert_monitor_only_no_worker_checks() {
let a = Assert::NO_OVERRIDES
.max_imbalance_ratio(5.0)
.fail_on_stall(true);
assert!(!a.has_worker_checks());
}
#[test]
fn assert_merge_all_field_categories() {
let defaults = Assert::default_checks();
let sched = Assert::NO_OVERRIDES
.max_spread_pct(50.0)
.max_p99_wake_latency_ns(100_000)
.max_migration_ratio(0.5)
.fail_on_stall(true);
let test = Assert::NO_OVERRIDES.check_isolation().max_spread_pct(80.0);
let merged = defaults.merge(&sched).merge(&test);
assert_eq!(merged.max_spread_pct, Some(80.0));
assert_eq!(merged.max_p99_wake_latency_ns, Some(100_000));
assert_eq!(merged.max_migration_ratio, Some(0.5));
assert_eq!(merged.isolation, Some(true));
assert_eq!(merged.fail_on_stall, Some(true));
}
#[test]
fn expect_scx_bpf_error_contains_builder_sets_field() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config");
assert_eq!(a.expect_scx_bpf_error_contains, Some("apply_cell_config"));
assert_eq!(a.expect_scx_bpf_error_matches, None);
}
#[test]
#[should_panic(expected = "must be non-empty")]
fn expect_scx_bpf_error_contains_builder_panics_on_empty() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("");
}
#[test]
fn expect_scx_bpf_error_matches_builder_sets_field() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply_cell_config.*-EINVAL");
assert_eq!(
a.expect_scx_bpf_error_matches,
Some(r"apply_cell_config.*-EINVAL"),
);
assert_eq!(a.expect_scx_bpf_error_contains, None);
}
#[test]
#[should_panic(expected = "must be non-empty")]
fn expect_scx_bpf_error_matches_builder_panics_on_empty() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches("");
}
#[test]
fn evaluate_scx_bpf_error_match_returns_empty_when_no_matcher_configured() {
let a = Assert::NO_OVERRIDES;
assert!(
a.evaluate_scx_bpf_error_match("some scheduler text", true)
.is_empty(),
);
assert!(
a.evaluate_scx_bpf_error_match("some scheduler text", false)
.is_empty(),
);
assert!(a.evaluate_scx_bpf_error_match("", true).is_empty());
}
#[test]
fn evaluate_scx_bpf_error_match_rejects_when_expect_err_unset() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("anything");
let details = a.evaluate_scx_bpf_error_match("text including anything substring", false);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(
msg.contains("expect_err = true"),
"misuse diagnostic must name expect_err: {msg}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_contains_passes_on_substring_present() {
let a =
Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config returned -EINVAL");
let corpus = "scx_mitosis: apply_cell_config returned -EINVAL at line:42; cell_id=3\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert!(
details.is_empty(),
"matcher must pass on substring match: {details:?}"
);
}
#[test]
fn evaluate_scx_bpf_error_match_contains_fails_on_absent_substring() {
let a =
Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config returned -EINVAL");
let corpus = "scx_mitosis: a DIFFERENT bug fired: stuck task X for 5000ms\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(
msg.contains("apply_cell_config returned -EINVAL"),
"diagnostic must name expected literal: {msg}",
);
assert!(
msg.contains("a DIFFERENT bug fired"),
"diagnostic must include the actual captured corpus: {msg}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_regex_passes_on_match() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply_cell_config.*-E[A-Z]+");
let corpus = "scx_mitosis: apply_cell_config returned -EINVAL at line:42\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert!(details.is_empty(), "regex matcher must pass: {details:?}");
}
#[test]
fn evaluate_scx_bpf_error_match_regex_fails_on_mismatch() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply_cell_config.*-E[A-Z]+");
let corpus = "scx_mitosis: stuck task for 5000ms\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(msg.contains("apply_cell_config"));
assert!(msg.contains("stuck task"));
}
#[test]
#[should_panic(expected = "is not valid regex")]
fn evaluate_scx_bpf_error_match_regex_invalid_pattern_panics_at_construction() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches("[unclosed");
}
#[test]
fn evaluate_scx_bpf_error_match_both_set_and_both_match_passes() {
let a = Assert::NO_OVERRIDES
.expect_scx_bpf_error_contains("apply_cell_config")
.expect_scx_bpf_error_matches(r"returned -E[A-Z]+");
let corpus = "scx_mitosis: apply_cell_config returned -EINVAL\n";
assert!(a.evaluate_scx_bpf_error_match(corpus, true).is_empty());
}
#[test]
fn evaluate_scx_bpf_error_match_both_set_one_mismatches_fails() {
let a = Assert::NO_OVERRIDES
.expect_scx_bpf_error_contains("apply_cell_config")
.expect_scx_bpf_error_matches(r"returned -E[A-Z]+");
let corpus = "scx_mitosis: apply_cell_config but no errno here\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert_eq!(details.len(), 1);
assert!(details[0].message.contains("returned -E"));
}
#[test]
fn evaluate_scx_bpf_error_match_finds_pattern_in_later_event() {
let corpus = "scx_mitosis: first different error at line:1\n\
sched_ext: BPF scheduler \"scx_mitosis\" disabled (Error)\n\
scx_mitosis: apply_cell_config returned -EINVAL at line:42\n\
sched_ext: BPF scheduler \"scx_mitosis\" disabled (Error)\n";
let a_lit =
Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config returned -EINVAL");
let lit_details = a_lit.evaluate_scx_bpf_error_match(corpus, true);
assert!(
lit_details.is_empty(),
"contains matcher must scan the WHOLE concatenated corpus, not stop \
at the first event boundary: {lit_details:?}",
);
let a_re = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply_cell_config.*-E[A-Z]+");
let re_details = a_re.evaluate_scx_bpf_error_match(corpus, true);
assert!(
re_details.is_empty(),
"regex matcher must scan the WHOLE concatenated corpus: {re_details:?}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_contains_fails_on_empty_corpus() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config");
let details = a.evaluate_scx_bpf_error_match("", true);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(
msg.contains("expect_scx_bpf_error_contains"),
"diagnostic must name the matcher: {msg}",
);
assert!(
msg.contains("apply_cell_config"),
"diagnostic must quote the expected literal: {msg}",
);
assert!(
msg.contains("0 bytes"),
"diagnostic must surface the corpus length so operators can distinguish \
empty-capture from matcher-mismatch: {msg}",
);
assert!(
msg.contains("up to 400 bytes follow:"),
"diagnostic must include the standard excerpt suffix even when empty: {msg}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_regex_fails_on_empty_corpus() {
let pattern = r"apply_cell_config.*-EINVAL";
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(pattern);
let details = a.evaluate_scx_bpf_error_match("", true);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(
msg.contains("expect_scx_bpf_error_matches"),
"diagnostic must name the matcher: {msg}",
);
assert!(
msg.contains(pattern),
"diagnostic must quote the pattern: {msg}",
);
assert!(
msg.contains("0 bytes"),
"diagnostic must surface the corpus length: {msg}",
);
assert!(
msg.contains("up to 400 bytes follow:"),
"diagnostic must include the standard excerpt suffix: {msg}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_both_fail_on_empty_corpus() {
let pattern = r"apply_cell_config.*-EINVAL";
let a = Assert::NO_OVERRIDES
.expect_scx_bpf_error_contains("apply_cell_config")
.expect_scx_bpf_error_matches(pattern);
let details = a.evaluate_scx_bpf_error_match("", true);
assert_eq!(
details.len(),
2,
"both matchers must produce independent failure details on empty corpus; got {details:?}",
);
assert!(
details
.iter()
.any(|d| d.message.contains("expect_scx_bpf_error_contains")),
"one detail must name the contains matcher: {details:?}",
);
assert!(
details
.iter()
.any(|d| d.message.contains("expect_scx_bpf_error_matches")),
"the other detail must name the regex matcher: {details:?}",
);
for d in &details {
assert!(
d.message.contains("0 bytes"),
"every detail must include the corpus-size diagnostic: {}",
d.message,
);
}
}
#[test]
fn evaluate_scx_bpf_error_match_excerpt_byte_truncated_at_char_boundary() {
let mut corpus = String::new();
for _ in 0..200 {
corpus.push('\u{2014}'); }
assert_eq!(corpus.len(), 600, "test fixture: 200 em-dashes = 600 bytes");
let assert_excerpt_within_budget = |msg: &str| {
let after_marker = msg
.split_once("up to 400 bytes follow:\n")
.unwrap_or_else(|| panic!("diagnostic missing excerpt marker: {msg}"))
.1;
assert_eq!(
after_marker.len(),
399,
"200 em-dashes at 3 bytes each: 133 fit in 399 bytes; 134 would overflow at 402",
);
assert!(
after_marker.chars().all(|c| c == '\u{2014}'),
"excerpt must contain only em-dashes (no replacement char or partial codepoint): {after_marker:?}",
);
};
let literal = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("not present");
let literal_details = literal.evaluate_scx_bpf_error_match(&corpus, true);
assert_eq!(literal_details.len(), 1);
assert_excerpt_within_budget(&literal_details[0].message);
let regex = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"not present anywhere");
let regex_details = regex.evaluate_scx_bpf_error_match(&corpus, true);
assert_eq!(regex_details.len(), 1);
assert_excerpt_within_budget(®ex_details[0].message);
}
#[test]
fn evaluate_scx_bpf_error_match_contains_no_trim_on_literal() {
let leading = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains(" leading spaces");
let no_match = leading.evaluate_scx_bpf_error_match("the line: leading spaces here", true);
assert_eq!(
no_match.len(),
1,
"literal with two leading spaces must NOT match corpus where the only \
instance lacks the leading spaces; got {no_match:?}",
);
let matches =
leading.evaluate_scx_bpf_error_match("prefix\n leading spaces appear here", true);
assert!(
matches.is_empty(),
"literal with leading spaces must match when corpus has them byte-for-byte; got {matches:?}",
);
let trailing = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("trailing ");
let no_trail_match = trailing.evaluate_scx_bpf_error_match("some trailing text", true);
assert_eq!(
no_trail_match.len(),
1,
"literal with two trailing spaces must NOT match corpus where the base \
token is followed by one space (catches hypothetical trim regression): \
{no_trail_match:?}",
);
let trail_match = trailing.evaluate_scx_bpf_error_match("some trailing text", true);
assert!(
trail_match.is_empty(),
"literal with two trailing spaces must match corpus with two spaces \
byte-for-byte: {trail_match:?}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_contains_unaffected_by_newlines_and_tabs() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_config");
let corpus = "first line\n\tindented tab here apply_cell_config more\n";
let details = a.evaluate_scx_bpf_error_match(corpus, true);
assert!(
details.is_empty(),
"literal match must succeed across embedded whitespace surroundings: {details:?}",
);
let with_nl = Assert::NO_OVERRIDES.expect_scx_bpf_error_contains("apply_cell_conf\nig");
let nl_details = with_nl.evaluate_scx_bpf_error_match("apply_cell_config", true);
assert_eq!(
nl_details.len(),
1,
"literal containing a newline must NOT match a corpus without that newline: {nl_details:?}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_regex_anchors_are_string_boundaries_by_default() {
let dollar = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply_cell_config$");
let dollar_mid = "apply_cell_config\nbut more after";
let dollar_mid_details = dollar.evaluate_scx_bpf_error_match(dollar_mid, true);
assert_eq!(
dollar_mid_details.len(),
1,
"default-mode $ anchors to STRING-end; mid-corpus token must NOT match: {dollar_mid_details:?}",
);
let dollar_end = "prefix junk\napply_cell_config";
let dollar_end_details = dollar.evaluate_scx_bpf_error_match(dollar_end, true);
assert!(
dollar_end_details.is_empty(),
"default-mode $ matches when corpus actually ends with the token: {dollar_end_details:?}",
);
let dollar_multiline =
Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"(?m)apply_cell_config$");
let dollar_multi_details = dollar_multiline.evaluate_scx_bpf_error_match(dollar_mid, true);
assert!(
dollar_multi_details.is_empty(),
"(?m) opt-in makes $ match line-end; mid-corpus token at line-end matches: {dollar_multi_details:?}",
);
let caret = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"^apply_cell_config");
let caret_mid = "prefix junk\napply_cell_config more";
let caret_mid_details = caret.evaluate_scx_bpf_error_match(caret_mid, true);
assert_eq!(
caret_mid_details.len(),
1,
"default-mode ^ anchors to STRING-start; mid-corpus token must NOT match: {caret_mid_details:?}",
);
let caret_start = "apply_cell_config\nbut more after";
let caret_start_details = caret.evaluate_scx_bpf_error_match(caret_start, true);
assert!(
caret_start_details.is_empty(),
"default-mode ^ matches when corpus actually starts with the token: {caret_start_details:?}",
);
let caret_multiline =
Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"(?m)^apply_cell_config");
let caret_multi_details = caret_multiline.evaluate_scx_bpf_error_match(caret_mid, true);
assert!(
caret_multi_details.is_empty(),
"(?m) opt-in makes ^ match line-start; mid-corpus token at line-start matches: {caret_multi_details:?}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_regex_dot_excludes_newline_by_default() {
let a = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"apply.*EINVAL");
let split = "apply_cell_config triggered\n-EINVAL returned";
let split_details = a.evaluate_scx_bpf_error_match(split, true);
assert_eq!(
split_details.len(),
1,
"default-mode . excludes \\n; pattern spanning lines must NOT match: {split_details:?}",
);
let same_line = "apply_cell_config triggered -EINVAL returned";
let same_details = a.evaluate_scx_bpf_error_match(same_line, true);
assert!(
same_details.is_empty(),
"default-mode . matches non-newline bytes; same-line pattern matches: {same_details:?}",
);
let a_dotall = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"(?s)apply.*EINVAL");
let dotall_details = a_dotall.evaluate_scx_bpf_error_match(split, true);
assert!(
dotall_details.is_empty(),
"(?s) DOTALL opt-in makes . cross newlines; split-line pattern matches: {dotall_details:?}",
);
}
#[test]
#[should_panic(expected = "matches the empty string")]
fn evaluate_scx_bpf_error_match_rejects_vacuous_optional() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"a?");
}
#[test]
#[should_panic(expected = "matches the empty string")]
fn evaluate_scx_bpf_error_match_rejects_vacuous_star() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r".*");
}
#[test]
#[should_panic(expected = "matches the empty string")]
fn evaluate_scx_bpf_error_match_rejects_vacuous_empty_group() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"(?:)");
}
#[test]
#[should_panic(expected = "matches the empty string")]
fn evaluate_scx_bpf_error_match_rejects_vacuous_anchored_empty() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"^$");
}
#[test]
#[should_panic(expected = "matches the empty string")]
fn evaluate_scx_bpf_error_match_rejects_vacuous_alternation_with_empty_branch() {
let _ = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches(r"(a|)");
}
#[test]
fn evaluate_scx_bpf_error_match_regex_invalid_pattern_via_direct_construction_fails_loudly() {
let a = Assert {
expect_scx_bpf_error_matches: Some("[unclosed"),
..Assert::NO_OVERRIDES
};
let details = a.evaluate_scx_bpf_error_match("corpus text", true);
assert_eq!(details.len(), 1);
let msg = &details[0].message;
assert!(
msg.contains("[unclosed"),
"invalid-pattern diagnostic must name the pattern: {msg}",
);
assert!(
msg.contains("regex compilation failed"),
"invalid-pattern diagnostic must say the pattern failed to compile: {msg}",
);
}
#[test]
fn evaluate_scx_bpf_error_match_regex_literal_whitespace_matches_byte_for_byte() {
let a_tab = Assert::NO_OVERRIDES.expect_scx_bpf_error_matches("apply\tcell");
let with_tab = "apply\tcell_config triggered";
let tab_match = a_tab.evaluate_scx_bpf_error_match(with_tab, true);
assert!(
tab_match.is_empty(),
"regex with literal tab must match corpus with literal tab: {tab_match:?}",
);
let with_space = "apply cell_config triggered";
let space_no_match = a_tab.evaluate_scx_bpf_error_match(with_space, true);
assert_eq!(
space_no_match.len(),
1,
"regex with literal tab must NOT match corpus with a space in that position: {space_no_match:?}",
);
}
#[test]
fn assert_implements_serialize_and_deserialize() {
fn requires<T: serde::Serialize + serde::de::DeserializeOwned>() {}
requires::<Assert>();
}
#[test]
fn assert_serde_roundtrip_preserves_every_non_skipped_field() {
let a = Assert {
not_starved: Some(true),
isolation: Some(true),
max_gap_ms: Some(1234),
max_spread_pct: Some(12.5),
max_throughput_cv: Some(0.42),
min_work_rate: Some(100.0),
max_p99_wake_latency_ns: Some(5_000_000),
max_wake_latency_cv: Some(0.7),
min_iteration_rate: Some(500.0),
max_migration_ratio: Some(0.05),
max_imbalance_ratio: Some(2.5),
max_local_dsq_depth: Some(64),
fail_on_stall: Some(true),
sustained_samples: Some(3),
max_fallback_rate: Some(0.01),
max_keep_last_rate: Some(0.005),
enforce_monitor_thresholds: true,
min_page_locality: Some(0.95),
max_cross_node_migration_ratio: Some(0.02),
max_slow_tier_ratio: Some(0.1),
expect_scx_bpf_error_contains: None,
expect_scx_bpf_error_matches: None,
};
let json = serde_json::to_string(&a).unwrap();
let b: Assert = serde_json::from_str(&json).unwrap();
assert_eq!(a.not_starved, b.not_starved);
assert_eq!(a.isolation, b.isolation);
assert_eq!(a.max_gap_ms, b.max_gap_ms);
assert_eq!(a.max_spread_pct, b.max_spread_pct);
assert_eq!(a.max_throughput_cv, b.max_throughput_cv);
assert_eq!(a.min_work_rate, b.min_work_rate);
assert_eq!(a.max_p99_wake_latency_ns, b.max_p99_wake_latency_ns);
assert_eq!(a.max_wake_latency_cv, b.max_wake_latency_cv);
assert_eq!(a.min_iteration_rate, b.min_iteration_rate);
assert_eq!(a.max_migration_ratio, b.max_migration_ratio);
assert_eq!(a.max_imbalance_ratio, b.max_imbalance_ratio);
assert_eq!(a.max_local_dsq_depth, b.max_local_dsq_depth);
assert_eq!(a.fail_on_stall, b.fail_on_stall);
assert_eq!(a.sustained_samples, b.sustained_samples);
assert_eq!(a.max_fallback_rate, b.max_fallback_rate);
assert_eq!(a.max_keep_last_rate, b.max_keep_last_rate);
assert_eq!(a.enforce_monitor_thresholds, b.enforce_monitor_thresholds);
assert_eq!(a.min_page_locality, b.min_page_locality);
assert_eq!(
a.max_cross_node_migration_ratio,
b.max_cross_node_migration_ratio
);
assert_eq!(a.max_slow_tier_ratio, b.max_slow_tier_ratio);
}
#[test]
fn outcome_implements_expected_traits() {
fn requires<
T: Clone + std::fmt::Debug + PartialEq + Eq + serde::Serialize + serde::de::DeserializeOwned,
>() {
}
requires::<Outcome>();
}
#[test]
fn outcome_merge_precedence_three_variant_subset_fail_pass_skip() {
let d = AssertDetail::new(DetailKind::Other, "payload");
let pass = || Outcome::Pass;
let skip = || Outcome::Skip(d.clone());
let fail = || Outcome::Fail(d.clone());
assert!(skip().merge(pass()).is_pass());
assert!(pass().merge(skip()).is_pass());
assert!(skip().merge(fail()).is_fail());
assert!(fail().merge(skip()).is_fail());
assert!(pass().merge(fail()).is_fail());
assert!(fail().merge(pass()).is_fail());
assert!(pass().merge(pass()).is_pass());
assert!(skip().merge(skip()).is_skip());
assert!(fail().merge(fail()).is_fail());
}
#[test]
fn outcome_accessor_truth_table() {
let d = AssertDetail::new(DetailKind::Other, "x");
assert!(Outcome::Pass.is_pass());
assert!(!Outcome::Pass.is_skip());
assert!(!Outcome::Pass.is_fail());
assert!(!Outcome::Pass.is_inconclusive());
assert!(!Outcome::Skip(d.clone()).is_pass());
assert!(Outcome::Skip(d.clone()).is_skip());
assert!(!Outcome::Skip(d.clone()).is_fail());
assert!(!Outcome::Skip(d.clone()).is_inconclusive());
assert!(!Outcome::Fail(d.clone()).is_pass());
assert!(!Outcome::Fail(d.clone()).is_skip());
assert!(Outcome::Fail(d.clone()).is_fail());
assert!(!Outcome::Fail(d.clone()).is_inconclusive());
assert!(!Outcome::Inconclusive(d.clone()).is_pass());
assert!(!Outcome::Inconclusive(d.clone()).is_skip());
assert!(!Outcome::Inconclusive(d.clone()).is_fail());
assert!(Outcome::Inconclusive(d.clone()).is_inconclusive());
}
#[test]
fn outcome_merge_precedence_inconclusive_above_pass_below_fail() {
let d = AssertDetail::new(DetailKind::Other, "payload");
let pass = || Outcome::Pass;
let skip = || Outcome::Skip(d.clone());
let fail = || Outcome::Fail(d.clone());
let inconc = || Outcome::Inconclusive(d.clone());
assert!(inconc().merge(pass()).is_inconclusive());
assert!(pass().merge(inconc()).is_inconclusive());
assert!(inconc().merge(skip()).is_inconclusive());
assert!(skip().merge(inconc()).is_inconclusive());
assert!(inconc().merge(fail()).is_fail());
assert!(fail().merge(inconc()).is_fail());
assert!(inconc().merge(inconc()).is_inconclusive());
let left = AssertDetail::new(DetailKind::Migration, "first-inconc");
let right = AssertDetail::new(DetailKind::Benchmark, "second-inconc");
let Outcome::Inconclusive(d) = Outcome::Inconclusive(left).merge(Outcome::Inconclusive(right))
else {
panic!("Inconclusive+Inconclusive must yield Inconclusive");
};
assert_eq!(d.kind, DetailKind::Migration, "LEFT-wins on payload tie");
assert!(d.message.contains("first-inconc"));
}
#[test]
fn record_inconclusive_appears_in_inconclusive_details_and_is_not_pass() {
let mut r = AssertResult::pass();
r.record_inconclusive(AssertDetail::new(
DetailKind::Migration,
"denominator was zero",
));
assert!(!r.is_pass(), "inconclusive must not read as pass");
assert!(!r.is_fail());
assert!(!r.is_skip());
assert!(r.is_inconclusive());
let reasons: Vec<_> = r.inconclusive_details().collect();
assert_eq!(reasons.len(), 1);
assert_eq!(reasons[0].kind, DetailKind::Migration);
assert!(reasons[0].message.contains("denominator was zero"));
assert_eq!(r.failure_details().count(), 0);
r.record_fail(AssertDetail::new(DetailKind::Other, "real failure"));
assert!(r.is_fail());
assert!(!r.is_inconclusive(), "Fail dominates Inconclusive");
}
#[test]
fn assert_result_outcome_folds_outcomes_vec() {
assert!(AssertResult::pass().outcome().is_pass());
let skip_result = AssertResult::skip("topology missing");
let Outcome::Skip(d) = skip_result.outcome() else {
panic!("expected Outcome::Skip, got {:?}", skip_result.outcome());
};
assert_eq!(d.kind, DetailKind::Skip);
assert!(d.message.contains("topology missing"));
let fail_result = AssertResult::fail(AssertDetail::new(DetailKind::Starved, "boom"));
let Outcome::Fail(d) = fail_result.outcome() else {
panic!("expected Outcome::Fail, got {:?}", fail_result.outcome());
};
assert_eq!(d.kind, DetailKind::Starved);
assert!(d.message.contains("boom"));
}
#[test]
fn assert_result_outcome_pass_plus_skip_is_pass_not_skip() {
let mut r = AssertResult::pass();
r.record_pass();
r.record_skip("optional probe");
assert!(matches!(r.outcome(), Outcome::Pass));
assert!(r.is_pass());
assert!(!r.is_skip());
}
#[test]
fn assert_result_outcome_multi_skip_returns_first_payload() {
let mut r = AssertResult::pass();
r.record_skip("first");
r.record_skip("second");
r.record_skip("third");
let Outcome::Skip(d) = r.outcome() else {
panic!("expected Skip, got {:?}", r.outcome());
};
assert!(
d.message.contains("first"),
"first-Skip-wins; got: {}",
d.message
);
}
#[test]
fn outcome_as_ref_preserves_discriminant_and_payload() {
assert!(matches!(Outcome::Pass.as_ref(), OutcomeRef::Pass));
let fail = Outcome::Fail(AssertDetail::new(DetailKind::Starved, "boom"));
let OutcomeRef::Fail(d) = fail.as_ref() else {
panic!("Fail as_ref should be Fail variant");
};
assert_eq!(d.kind, DetailKind::Starved);
assert!(d.message.contains("boom"));
let skip = Outcome::Skip(AssertDetail::new(DetailKind::Skip, "missing"));
let OutcomeRef::Skip(d) = skip.as_ref() else {
panic!("Skip as_ref should be Skip variant");
};
assert_eq!(d.kind, DetailKind::Skip);
assert!(d.message.contains("missing"));
let inconc =
Outcome::Inconclusive(AssertDetail::new(DetailKind::Migration, "zero denominator"));
let OutcomeRef::Inconclusive(d) = inconc.as_ref() else {
panic!("Inconclusive as_ref should be Inconclusive variant");
};
assert_eq!(d.kind, DetailKind::Migration);
assert!(d.message.contains("zero denominator"));
}
#[test]
fn assert_result_outcome_ref_matches_owned_outcome_shape() {
assert!(matches!(
AssertResult::pass().outcome_ref(),
OutcomeRef::Pass
));
let mut all_skip = AssertResult::pass();
all_skip.record_skip("only-skip");
let OutcomeRef::Skip(d) = all_skip.outcome_ref() else {
panic!("all-Skip stream should yield Skip");
};
assert!(d.message.contains("only-skip"));
let mut mixed = AssertResult::pass();
mixed.record_pass();
mixed.record_skip("demoted");
assert!(matches!(mixed.outcome_ref(), OutcomeRef::Pass));
let mut fail = AssertResult::pass();
fail.record_fail(AssertDetail::new(DetailKind::Stuck, "first-fail"));
fail.record_fail(AssertDetail::new(DetailKind::Starved, "second-fail"));
let OutcomeRef::Fail(d) = fail.outcome_ref() else {
panic!("any-Fail stream should yield Fail");
};
assert_eq!(d.kind, DetailKind::Stuck);
assert!(d.message.contains("first-fail"));
let mut all_inconc = AssertResult::pass();
all_inconc.record_inconclusive(AssertDetail::new(DetailKind::Migration, "first-inconc"));
all_inconc.record_inconclusive(AssertDetail::new(DetailKind::Benchmark, "second-inconc"));
let OutcomeRef::Inconclusive(d) = all_inconc.outcome_ref() else {
panic!("all-Inconclusive stream should yield Inconclusive");
};
assert_eq!(d.kind, DetailKind::Migration);
assert!(d.message.contains("first-inconc"));
let mut fail_over_inconc = AssertResult::pass();
fail_over_inconc
.record_inconclusive(AssertDetail::new(DetailKind::Migration, "denominator-zero"));
fail_over_inconc.record_fail(AssertDetail::new(DetailKind::Stuck, "real-fail"));
let OutcomeRef::Fail(d) = fail_over_inconc.outcome_ref() else {
panic!("Fail+Inconclusive stream should yield Fail");
};
assert_eq!(d.kind, DetailKind::Stuck);
assert!(d.message.contains("real-fail"));
let mut inconc_over_skip = AssertResult::pass();
inconc_over_skip.record_inconclusive(AssertDetail::new(DetailKind::Benchmark, "left-inconc"));
inconc_over_skip.record_skip("right-skip");
let OutcomeRef::Inconclusive(d) = inconc_over_skip.outcome_ref() else {
panic!(
"Inconclusive+Skip stream should yield Inconclusive (Inconclusive > Skip): got {:?}",
inconc_over_skip.outcome_ref()
);
};
assert_eq!(d.kind, DetailKind::Benchmark);
assert!(d.message.contains("left-inconc"));
let mut skip_then_inconc = AssertResult::pass();
skip_then_inconc.record_skip("left-skip");
skip_then_inconc.record_inconclusive(AssertDetail::new(DetailKind::Benchmark, "right-inconc"));
let OutcomeRef::Inconclusive(d) = skip_then_inconc.outcome_ref() else {
panic!(
"Skip+Inconclusive stream should still yield Inconclusive: got {:?}",
skip_then_inconc.outcome_ref()
);
};
assert_eq!(d.kind, DetailKind::Benchmark);
assert!(d.message.contains("right-inconc"));
}
#[test]
fn assert_result_record_fail_appends_and_folds_fail() {
let mut r = AssertResult::pass();
assert!(r.is_pass(), "fresh AssertResult::pass is_pass");
r.record_fail(AssertDetail::new(DetailKind::Starved, "first"));
r.record_fail(AssertDetail::new(DetailKind::Stuck, "second"));
assert_eq!(r.outcomes.len(), 2);
assert!(r.is_fail());
let Outcome::Fail(d) = r.outcome() else {
panic!("expected Outcome::Fail, got {:?}", r.outcome());
};
assert_eq!(d.kind, DetailKind::Starved);
assert!(d.message.contains("first"));
let collected: Vec<&AssertDetail> = r.failure_details().collect();
assert_eq!(collected.len(), 2);
}
#[test]
fn assert_result_record_skip_then_pass_marker_yields_pass() {
let mut r = AssertResult::pass();
r.record_skip("topology mismatch");
assert!(r.is_skip(), "skip-only stream is is_skip");
r.record_pass();
assert!(r.is_pass(), "Skip + Pass folds to Pass");
assert!(!r.is_skip(), "is_skip requires all-Skip + non-empty");
}
#[test]
fn assert_result_empty_outcomes_is_pass_not_skip() {
let r = AssertResult::pass();
assert!(r.outcomes.is_empty());
assert!(r.is_pass());
assert!(!r.is_skip());
assert!(!r.is_fail());
}
#[test]
fn assert_result_record_outcome_pushes_and_observable() {
let mut r = AssertResult::pass();
let d = AssertDetail::new(DetailKind::Other, "external verdict");
r.record_outcome(Outcome::Fail(d.clone()));
assert_eq!(r.outcomes.len(), 1);
assert!(r.is_fail());
let collected: Vec<&AssertDetail> = r.failure_details().collect();
assert_eq!(collected.len(), 1);
assert!(collected[0].message.contains("external verdict"));
r.record_outcome(Outcome::Skip(AssertDetail::new(DetailKind::Skip, "stop")));
assert_eq!(r.outcomes.len(), 2);
assert!(r.is_fail(), "Fail dominates the merged outcome");
assert_eq!(r.skip_details().count(), 1);
}
#[test]
fn outcome_serde_externally_tagged_roundtrips_via_json_and_postcard() {
let d = AssertDetail::new(DetailKind::Other, "msg");
let pass_json = serde_json::to_string(&Outcome::Pass).unwrap();
assert_eq!(
pass_json, "\"Pass\"",
"Pass must serialize as bare variant name"
);
let fail_json = serde_json::to_string(&Outcome::Fail(d.clone())).unwrap();
assert!(
fail_json.starts_with("{\"Fail\":"),
"Fail must serialize as externally-tagged object: {fail_json}"
);
assert!(
!fail_json.starts_with("{\"kind\":"),
"Fail outer key must be the variant name, not the dropped \"kind\" tag: {fail_json}"
);
assert!(
!fail_json.contains("\"data\":"),
"Fail must not carry the dropped \"data\" wrapper: {fail_json}"
);
let recovered: Outcome = serde_json::from_str(&fail_json).unwrap();
assert!(recovered.is_fail());
let inconc_detail = AssertDetail::new(DetailKind::Migration, "zero denom");
let inconc_json = serde_json::to_string(&Outcome::Inconclusive(inconc_detail.clone())).unwrap();
assert!(
inconc_json.starts_with("{\"Inconclusive\":"),
"Inconclusive must serialize as externally-tagged object: {inconc_json}"
);
assert!(
!inconc_json.contains("\"data\":"),
"Inconclusive must not carry the dropped \"data\" wrapper: {inconc_json}"
);
let inconc_recovered: Outcome = serde_json::from_str(&inconc_json).unwrap();
assert!(inconc_recovered.is_inconclusive());
let pc_pass = postcard::to_stdvec(&Outcome::Pass).unwrap();
let pc_pass_recovered: Outcome = postcard::from_bytes(&pc_pass).unwrap();
assert!(pc_pass_recovered.is_pass());
let pc_fail = postcard::to_stdvec(&Outcome::Fail(d)).unwrap();
let pc_fail_recovered: Outcome = postcard::from_bytes(&pc_fail).unwrap();
assert!(pc_fail_recovered.is_fail());
let pc_inconc = postcard::to_stdvec(&Outcome::Inconclusive(inconc_detail)).unwrap();
let pc_inconc_recovered: Outcome = postcard::from_bytes(&pc_inconc).unwrap();
assert!(pc_inconc_recovered.is_inconclusive());
}
#[test]
fn outcome_postcard_variant_index_byte_sentinel() {
let d = AssertDetail::new(DetailKind::Other, "x");
let pc_pass = postcard::to_stdvec(&Outcome::Pass).unwrap();
assert_eq!(
pc_pass.first().copied(),
Some(0u8),
"Outcome::Pass MUST encode with leading variant-index byte 0 (got bytes {pc_pass:?}); \
a variant reorder would silently corrupt the TLV wire path",
);
let pc_skip = postcard::to_stdvec(&Outcome::Skip(d.clone())).unwrap();
assert_eq!(
pc_skip.first().copied(),
Some(1u8),
"Outcome::Skip MUST encode with leading variant-index byte 1 (got bytes {pc_skip:?})",
);
let pc_inconc = postcard::to_stdvec(&Outcome::Inconclusive(d.clone())).unwrap();
assert_eq!(
pc_inconc.first().copied(),
Some(2u8),
"Outcome::Inconclusive MUST encode with leading variant-index byte 2 \
(got bytes {pc_inconc:?})",
);
let pc_fail = postcard::to_stdvec(&Outcome::Fail(d)).unwrap();
assert_eq!(
pc_fail.first().copied(),
Some(3u8),
"Outcome::Fail MUST encode with leading variant-index byte 3 (got bytes {pc_fail:?})",
);
}
#[test]
fn assert_serde_skips_reproducer_string_fields() {
let a = Assert::NO_OVERRIDES
.expect_scx_bpf_error_contains("apply_cell_config")
.expect_scx_bpf_error_matches(r"\bEINVAL\b");
let json = serde_json::to_string(&a).unwrap();
assert!(
!json.contains("expect_scx_bpf_error_contains"),
"expect_scx_bpf_error_contains must NOT appear in serialized JSON; got: {json}"
);
assert!(
!json.contains("expect_scx_bpf_error_matches"),
"expect_scx_bpf_error_matches must NOT appear in serialized JSON; got: {json}"
);
let b: Assert = serde_json::from_str(&json).unwrap();
assert_eq!(
b.expect_scx_bpf_error_contains, None,
"deserialized reproducer-contains field must default to None"
);
assert_eq!(
b.expect_scx_bpf_error_matches, None,
"deserialized reproducer-matches field must default to None"
);
}