#![cfg(test)]
use super::*;
use crate::monitor::debug_capture::WorkloadGroupHint;
#[test]
fn generate_spec_empty_fingerprint() {
let cap = DebugCapture::default();
let spec = generate_spec(&cap);
assert_eq!(spec.config.num_workers, 1);
assert!(matches!(spec.config.affinity, AffinityIntent::Inherit));
assert!(matches!(spec.config.work_type, WorkType::SpinWait));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("no workload groups"))
);
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("no work-type hint"))
);
}
#[test]
fn generate_spec_thread_count_to_workers() {
let mut cap = DebugCapture::default();
cap.fingerprint.workload_groups = vec![WorkloadGroupHint {
cgroup_path: "/test".into(),
thread_count: 8,
cpu_time_fraction: 0.5,
wakeups_per_sec: 100.0,
}];
let spec = generate_spec(&cap);
assert_eq!(spec.config.num_workers, 8);
}
#[test]
fn generate_spec_exact_affinity() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::Exact {
cpus: vec![0, 1, 4, 5],
}];
let spec = generate_spec(&cap);
match spec.config.affinity {
AffinityIntent::Exact(set) => {
let v: Vec<usize> = set.into_iter().collect();
assert_eq!(v, vec![0, 1, 4, 5]);
}
other => panic!("expected Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::Exact")
&& n.message().contains("with resolved CPUs")),
"populated Exact must emit a resolved-collapse note for surface consistency: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_exact_empty_emits_unresolved_note() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::Exact { cpus: Vec::new() }];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::Exact(set) => {
assert!(
set.is_empty(),
"empty Exact must propagate through to AffinityIntent: {set:?}"
);
}
other => panic!("expected empty Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::Exact")
&& n.message().contains("no CPUs")),
"empty Exact must surface a hand-edit-required note: {:?}",
spec.notes,
);
assert!(
spec.notes.iter().any(|n| n
.message()
.contains("AffinityIntent::exact([<cpu_0>, <cpu_1>, ...])")),
"empty Exact note must include paste-ready Rust hand-edit target: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_bursty_passthrough() {
let mut cap = DebugCapture::default();
cap.fingerprint.work_type_hints = vec![WorkTypeHint::Bursty {
burst_duration: Duration::from_millis(5),
sleep_duration: Duration::from_millis(95),
}];
let spec = generate_spec(&cap);
match spec.config.work_type {
WorkType::Bursty {
burst_duration,
sleep_duration,
} => {
assert_eq!(burst_duration, Duration::from_millis(5));
assert_eq!(sleep_duration, Duration::from_millis(95));
}
other => panic!("expected Bursty, got {other:?}"),
}
}
#[test]
fn generate_spec_fifo_priority() {
let mut cap = DebugCapture::default();
cap.fingerprint.sched_policy_hints = vec![SchedPolicyHint::Fifo { priority: 50 }];
let spec = generate_spec(&cap);
match spec.config.sched_policy {
SchedPolicy::Fifo(prio) => assert_eq!(prio, 50),
other => panic!("expected Fifo, got {other:?}"),
}
}
#[test]
fn generate_spec_nice_applied() {
let mut cap = DebugCapture::default();
cap.fingerprint.sched_policy_hints = vec![SchedPolicyHint::Other { nice: 5 }];
let spec = generate_spec(&cap);
assert!(matches!(spec.config.sched_policy, SchedPolicy::Normal));
assert_eq!(spec.config.nice, Some(5));
}
#[test]
fn generate_spec_multiple_hints_first_wins() {
let mut cap = DebugCapture::default();
cap.fingerprint.work_type_hints = vec![WorkTypeHint::SpinWait, WorkTypeHint::IoSyncWrite];
let spec = generate_spec(&cap);
assert!(matches!(spec.config.work_type, WorkType::SpinWait));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("additional work-type hints"))
);
}
#[test]
fn generate_spec_maps_each_io_hint_directly() {
let mut cap = DebugCapture::default();
cap.fingerprint.work_type_hints = vec![WorkTypeHint::IoRandRead];
let spec = generate_spec(&cap);
assert!(
matches!(spec.config.work_type, WorkType::IoRandRead),
"IoRandRead hint must map to WorkType::IoRandRead, got {:?}",
spec.config.work_type,
);
let mut cap = DebugCapture::default();
cap.fingerprint.work_type_hints = vec![WorkTypeHint::IoConvoy];
let spec = generate_spec(&cap);
assert!(
matches!(spec.config.work_type, WorkType::IoConvoy),
"IoConvoy hint must map to WorkType::IoConvoy, got {:?}",
spec.config.work_type,
);
let mut cap = DebugCapture::default();
cap.fingerprint.work_type_hints = vec![WorkTypeHint::IoSyncWrite];
let spec = generate_spec(&cap);
assert!(
matches!(spec.config.work_type, WorkType::IoSyncWrite),
"IoSyncWrite hint must map to WorkType::IoSyncWrite, got {:?}",
spec.config.work_type,
);
}
#[test]
fn generate_spec_propagates_gaps() {
let mut cap = DebugCapture::default();
cap.fingerprint
.gaps
.push("affinity hint backed by 1 sample".into());
let spec = generate_spec(&cap);
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("affinity hint backed by 1 sample"))
);
}
#[test]
fn generate_spec_llc_aligned_unresolved_emits_topology_aware() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::LlcAligned { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(matches!(spec.config.affinity, AffinityIntent::LlcAligned));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::LlcAligned")
&& n.message().contains("without resolved CPUs")),
"unresolved LlcAligned must surface a topology-aware-fallback note: {:?}",
spec.notes,
);
assert!(
spec.notes.iter().any(|n| n
.message()
.contains("AffinityIntent::exact([<llc_cpu_0>, <llc_cpu_1>, ...])")),
"unresolved LlcAligned note must include paste-ready Rust hand-edit target: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_llc_aligned_resolved_emits_exact() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::LlcAligned {
cpus: vec![0, 1, 2, 3],
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::Exact(set) => {
let v: Vec<usize> = set.iter().copied().collect();
assert_eq!(
v,
vec![0, 1, 2, 3],
"resolved LlcAligned must collapse to Exact with the observed CPUs: got {v:?}",
);
}
other => panic!("expected Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::LlcAligned")
&& n.message().contains("with resolved CPUs")),
"resolved LlcAligned must surface a resolved-collapse note: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_single_cpu_resolved_emits_exact() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::SingleCpu { cpus: vec![7] }];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::Exact(set) => {
let v: Vec<usize> = set.iter().copied().collect();
assert_eq!(v, vec![7]);
}
other => panic!("expected Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::SingleCpu")
&& n.message().contains("with resolved CPUs")),
"resolved SingleCpu must surface a resolved-collapse note: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_single_cpu_unresolved_emits_topology_aware() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::SingleCpu { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(matches!(spec.config.affinity, AffinityIntent::SingleCpu));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::SingleCpu")
&& n.message().contains("without resolved CPUs")),
);
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityIntent::exact([<cpu>])")),
"unresolved SingleCpu note must include paste-ready Rust hand-edit target: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_cross_cgroup_resolved_emits_exact() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::CrossCgroup {
cpus: vec![2, 4, 6, 8],
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::Exact(set) => {
let v: Vec<usize> = set.iter().copied().collect();
assert_eq!(v, vec![2, 4, 6, 8]);
}
other => panic!("expected Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::CrossCgroup")
&& n.message().contains("with resolved CPUs")),
);
}
#[test]
fn generate_spec_cross_cgroup_unresolved_emits_topology_aware() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::CrossCgroup { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(matches!(spec.config.affinity, AffinityIntent::CrossCgroup));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::CrossCgroup")
&& n.message().contains("without resolved CPUs")),
"unresolved CrossCgroup must surface a topology-aware-fallback note: {:?}",
spec.notes,
);
assert!(
spec.notes.iter().any(|n| n
.message()
.contains("AffinityIntent::exact([<cpu_0>, <cpu_1>, ...])")),
"unresolved CrossCgroup note must include paste-ready Rust hand-edit target: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_random_subset_resolved_emits_populated() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: vec![0, 1, 2, 3, 4, 5],
count: 3,
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::RandomSubset { from, count } => {
let v: Vec<usize> = from.iter().copied().collect();
assert_eq!(v, vec![0, 1, 2, 3, 4, 5]);
assert_eq!(*count, 3);
}
other => panic!("expected populated RandomSubset, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::RandomSubset")
&& n.message().contains("with resolved pool")),
);
}
#[test]
fn generate_spec_random_subset_unresolved_emits_placeholder() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: Vec::new(),
count: 0,
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::RandomSubset { from, count } => {
assert!(
from.is_empty(),
"unresolved RandomSubset must emit empty pool"
);
assert_eq!(*count, 0);
}
other => panic!("expected placeholder RandomSubset, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::RandomSubset")
&& n.message().contains("without a resolved pool")),
);
}
#[test]
fn generate_spec_random_subset_pool_without_count_is_placeholder() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: vec![0, 1, 2],
count: 0,
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::RandomSubset { from, count } => {
assert!(
from.is_empty(),
"(non_empty, 0) must drop pool to placeholder: got {from:?}",
);
assert_eq!(*count, 0);
}
other => panic!("expected placeholder RandomSubset, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::RandomSubset")
&& n.message().contains("without a resolved pool")),
"(non_empty, 0) must surface unresolved-pool note: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_random_subset_count_without_pool_is_placeholder() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: Vec::new(),
count: 3,
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::RandomSubset { from, count } => {
assert!(from.is_empty());
assert_eq!(
*count, 0,
"([], non_zero) must drop count to placeholder: got {count}",
);
}
other => panic!("expected placeholder RandomSubset, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::RandomSubset")
&& n.message().contains("without a resolved pool")),
"([], non_zero) must surface unresolved-pool note: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_random_subset_count_exceeds_pool_is_populated() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: vec![0, 1],
count: 10,
}];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::RandomSubset { from, count } => {
let v: Vec<usize> = from.iter().copied().collect();
assert_eq!(v, vec![0, 1]);
assert_eq!(
*count, 10,
"count > pool.len() must passthrough verbatim: got {count}",
);
}
other => panic!("expected populated RandomSubset, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::RandomSubset")
&& n.message().contains("with resolved pool")),
"count > pool.len() must take the resolved path: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_smt_sibling_pair_resolved_emits_exact() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::SmtSiblingPair { cpus: vec![2, 3] }];
let spec = generate_spec(&cap);
match &spec.config.affinity {
AffinityIntent::Exact(set) => {
let v: Vec<usize> = set.iter().copied().collect();
assert_eq!(v, vec![2, 3]);
}
other => panic!("expected Exact, got {other:?}"),
}
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::SmtSiblingPair")
&& n.message().contains("with resolved CPUs")),
"resolved SmtSiblingPair must surface a resolved-collapse note: {:?}",
spec.notes,
);
}
#[test]
fn generate_spec_smt_sibling_pair_unresolved_emits_topology_aware() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::SmtSiblingPair { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(matches!(
spec.config.affinity,
AffinityIntent::SmtSiblingPair
));
assert!(
spec.notes
.iter()
.any(|n| n.message().contains("AffinityHint::SmtSiblingPair")
&& n.message().contains("without resolved CPUs")),
"unresolved SmtSiblingPair must surface a topology-aware-fallback note: {:?}",
spec.notes,
);
assert!(
spec.notes.iter().any(|n| n
.message()
.contains("AffinityIntent::exact([<sibling_a>, <sibling_b>])")),
"unresolved SmtSiblingPair note must include paste-ready Rust hand-edit target: {:?}",
spec.notes,
);
}
#[test]
fn render_run_file_source_basic_shape() {
let mut cap = DebugCapture::default();
cap.fingerprint.workload_groups = vec![WorkloadGroupHint {
cgroup_path: "/test".into(),
thread_count: 4,
cpu_time_fraction: 0.0,
wakeups_per_sec: 0.0,
}];
cap.fingerprint.work_type_hints = vec![WorkTypeHint::SpinWait];
let spec = generate_spec(&cap);
assert!(
spec.notes.is_empty(),
"basic-shape fingerprint must produce no notes; got {:?}",
spec.notes,
);
let src = render_run_file_source(&spec, "regression_repro");
assert!(src.contains("use ktstr::workload::*;"));
assert!(src.contains("pub fn regression_repro"));
assert!(src.contains(".workers(4)"));
assert!(src.contains(".work_type(WorkType::SpinWait)"));
assert!(!src.contains("Generator notes:"));
}
#[test]
fn render_run_file_source_renders_notes() {
let mut cap = DebugCapture::default();
cap.fingerprint.gaps = vec!["test gap from fingerprint".into()];
let spec = generate_spec(&cap);
assert!(
!spec.notes.is_empty(),
"fingerprint gap must propagate to spec.notes",
);
let src = render_run_file_source(&spec, "with_notes");
assert!(src.contains("Generator notes:"));
assert!(src.contains("// - fingerprint gap: test gap from fingerprint"));
}
#[test]
fn render_ktstr_test_source_has_attribute() {
let cap = DebugCapture::default();
let spec = generate_spec(&cap);
let src = render_ktstr_test_source(&spec, "auto_repro");
assert!(src.contains("#[ktstr::ktstr_test]"));
assert!(src.contains("pub fn auto_repro"));
}
#[test]
fn render_run_file_source_emits_nice_only_when_some() {
let cap = DebugCapture::default();
let mut spec_none = generate_spec(&cap);
assert_eq!(spec_none.config.nice, None);
let src_none = render_run_file_source(&spec_none, "nice_none");
assert!(
!src_none.contains(".nice("),
"rendered source must skip `.nice(...)` when the spec carries \
`None` so the generated config lands at the type-level default: \
{src_none}",
);
spec_none.config.nice = Some(0);
let src_zero = render_run_file_source(&spec_none, "nice_zero");
assert!(
src_zero.contains(".nice(0)"),
"rendered source must emit `.nice(0)` when the spec carries \
`Some(0)` — `None` is the inherit state, `Some(0)` is the \
explicit override: {src_zero}",
);
}
#[test]
fn render_ktstr_test_source_template_name_substring_in_body() {
let cap = DebugCapture::default();
let spec = generate_spec(&cap);
let src = render_ktstr_test_source(&spec, "default");
let attribute = "#[ktstr::ktstr_test]";
let attribute_count = src.matches(attribute).count();
assert_eq!(
attribute_count, 1,
"attribute must be inserted exactly once, got {attribute_count} \
occurrences in: {src}",
);
assert!(
src.contains("WorkloadConfig::default()"),
"WorkloadConfig::default() must remain intact (substring \
replace must not match the `default()` body call): {src}",
);
assert!(
src.contains("#[ktstr::ktstr_test]\npub fn default"),
"rewritten `pub fn default` must carry the attribute: {src}",
);
}
#[test]
fn is_runnable_resolved_random_subset() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: vec![0, 1, 2],
count: 2,
}];
let spec = generate_spec(&cap);
assert!(
spec.is_runnable(),
"resolved RandomSubset must be runnable; notes: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 0);
}
#[test]
fn is_runnable_unresolved_single_cpu() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::SingleCpu { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(
!spec.is_runnable(),
"unresolved SingleCpu must NOT be runnable; notes: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 1);
}
#[test]
fn is_runnable_empty_exact() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::Exact { cpus: Vec::new() }];
let spec = generate_spec(&cap);
assert!(
!spec.is_runnable(),
"empty Exact must NOT be runnable; notes: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 1);
}
#[test]
fn is_runnable_unresolved_random_subset() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::RandomSubset {
from: Vec::new(),
count: 0,
}];
let spec = generate_spec(&cap);
assert!(
!spec.is_runnable(),
"unresolved RandomSubset must NOT be runnable; notes: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 1);
}
#[test]
fn is_runnable_empty_fingerprint() {
let cap = DebugCapture::default();
let spec = generate_spec(&cap);
assert!(
spec.is_runnable(),
"empty fingerprint must be runnable (default Inherit); notes: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 0);
}
#[test]
fn render_run_file_source_includes_cgroup_hints_as_comments() {
let mut cap = DebugCapture::default();
cap.fingerprint.cgroup_hints = vec![
CgroupHint {
path: "/system.slice/foo.service".into(),
cpu_weight: Some(200),
memory_max_bytes: Some(8 * 1024 * 1024 * 1024),
cpuset_cpus: vec![0, 1, 2, 3],
cpu_max_quota_us: None,
},
CgroupHint {
path: "/system.slice/bar.service".into(),
cpu_weight: Some(100),
memory_max_bytes: None,
cpuset_cpus: vec![4, 5],
cpu_max_quota_us: Some(75_000),
},
];
let spec = generate_spec(&cap);
let src = render_run_file_source(&spec, "with_cgroup");
assert!(src.contains("Cgroup hints"));
assert!(src.contains("/system.slice/foo.service"));
assert!(src.contains("weight=Some(200)"));
assert!(
src.contains("cpu_max_quota_us=None"),
"unlimited cpu_max_quota_us must render as None: {src}",
);
assert!(
src.contains("/system.slice/bar.service"),
"second cgroup hint must render: {src}",
);
assert!(
src.contains("cpu_max_quota_us=Some(75000)"),
"bounded cpu_max_quota_us must render its value: {src}",
);
}
#[test]
fn render_run_file_source_e2e_smoke() {
let mut cap = DebugCapture::default();
cap.fingerprint.workload_groups = vec![WorkloadGroupHint {
cgroup_path: "/system.slice/foo.service".into(),
thread_count: 16,
cpu_time_fraction: 0.65,
wakeups_per_sec: 850.0,
}];
cap.fingerprint.affinity_hints = vec![
AffinityHint::SmtSiblingPair { cpus: vec![4, 5] },
AffinityHint::Exact {
cpus: vec![0, 1, 2, 3],
},
];
cap.fingerprint.work_type_hints = vec![WorkTypeHint::Bursty {
burst_duration: Duration::from_millis(7),
sleep_duration: Duration::from_millis(43),
}];
cap.fingerprint.cgroup_hints = vec![CgroupHint {
path: "/system.slice/foo.service".into(),
cpu_weight: Some(150),
memory_max_bytes: Some(4 * 1024 * 1024 * 1024),
cpuset_cpus: vec![4, 5],
cpu_max_quota_us: Some(50_000),
}];
cap.fingerprint.sched_policy_hints = vec![SchedPolicyHint::Fifo { priority: 60 }];
cap.fingerprint.gaps = vec!["sample window had 2 dropouts".into()];
let spec1 = generate_spec(&cap);
let src1 = render_run_file_source(&spec1, "e2e_repro");
let spec2 = generate_spec(&cap);
let src2 = render_run_file_source(&spec2, "e2e_repro");
assert_eq!(
src1, src2,
"render_run_file_source must be deterministic for the same capture",
);
assert!(src1.contains("use ktstr::workload::*;"));
assert!(src1.contains("use std::collections::BTreeSet;"));
assert!(src1.contains("use std::time::Duration;"));
assert!(src1.contains("pub fn e2e_repro"));
assert!(
src1.contains(".workers(16)"),
"thread_count=16 must surface as .workers(16): {src1}",
);
assert!(
src1.contains(".affinity(AffinityIntent::Exact"),
"first affinity hint (SmtSiblingPair) must collapse to Exact: {src1}",
);
assert!(
src1.contains("BTreeSet::from([4, 5])"),
"Exact pool must contain the SmtSiblingPair CPUs: {src1}",
);
assert!(
src1.contains(".work_type(WorkType::Bursty"),
"Bursty work-type hint must reach the builder: {src1}",
);
assert!(
src1.contains("Duration::from_millis(7)") && src1.contains("Duration::from_millis(43)"),
"Bursty durations must surface in the rendered call: {src1}",
);
assert!(
src1.contains(".sched_policy(SchedPolicy::Fifo(60))"),
"Fifo priority must surface: {src1}",
);
assert!(
src1.contains("Cgroup hints"),
"cgroup hints must render as comments: {src1}",
);
assert!(
src1.contains("/system.slice/foo.service"),
"cgroup path must appear in the rendered comments: {src1}",
);
assert!(
src1.contains("cpu_max_quota_us=Some(50000)"),
"cpu_max_quota_us must render alongside the other cgroup \
fields (weight/mem_max/cpuset): {src1}",
);
assert!(
src1.contains("Generator notes:"),
"non-empty notes must trigger the comment block: {src1}",
);
assert!(
src1.contains("fingerprint gap: sample window had 2 dropouts"),
"fingerprint gap must propagate verbatim: {src1}",
);
assert!(
src1.contains("AffinityHint::SmtSiblingPair"),
"resolved-collapse note must cite the original variant: {src1}",
);
assert!(
src1.contains("additional affinity hints not modeled"),
"second affinity hint must surface as an additional-hints note: {src1}",
);
assert!(
!src1.contains("/* TODO:"),
"implemented work-type variants must not render any TODO placeholder: {src1}",
);
}
#[test]
fn render_affinity_all_variants() {
assert_eq!(
render_affinity(&AffinityIntent::Inherit),
"AffinityIntent::Inherit"
);
assert_eq!(
render_affinity(&AffinityIntent::SingleCpu),
"AffinityIntent::SingleCpu"
);
assert_eq!(
render_affinity(&AffinityIntent::LlcAligned),
"AffinityIntent::LlcAligned"
);
assert_eq!(
render_affinity(&AffinityIntent::CrossCgroup),
"AffinityIntent::CrossCgroup"
);
assert_eq!(
render_affinity(&AffinityIntent::SmtSiblingPair),
"AffinityIntent::SmtSiblingPair"
);
let random = AffinityIntent::RandomSubset {
from: BTreeSet::from([0usize, 1, 2, 3]),
count: 2,
};
assert_eq!(
render_affinity(&random),
"AffinityIntent::RandomSubset { from: BTreeSet::from([0, 1, 2, 3]), count: 2 }"
);
let exact = AffinityIntent::Exact(BTreeSet::from([0usize, 1, 2]));
assert_eq!(
render_affinity(&exact),
"AffinityIntent::Exact(BTreeSet::from([0, 1, 2]))"
);
}
#[test]
fn is_unmapped_work_type_split_matches_render() {
use crate::workload::{WorkPhase, WorkerReport};
use std::sync::atomic::AtomicBool;
fn stub_custom_fn(_: &AtomicBool) -> WorkerReport {
WorkerReport::default()
}
let mut all_variants: Vec<WorkType> = WorkType::ALL_NAMES
.iter()
.filter_map(|n| WorkType::from_name(n))
.collect();
all_variants.push(WorkType::Sequence {
first: WorkPhase::Spin(Duration::from_millis(1)),
rest: Vec::new(),
});
all_variants.push(WorkType::Custom {
name: "stub-custom".into(),
run: stub_custom_fn,
});
assert_eq!(
all_variants.len(),
WorkType::ALL_NAMES.len(),
"every WorkType variant must be constructed; \
ALL_NAMES = {:?}, constructed {} variants",
WorkType::ALL_NAMES,
all_variants.len(),
);
let mut runnable_count = 0usize;
let mut unmapped_count = 0usize;
for w in &all_variants {
let rendered = render_work_type(w);
if is_unmapped_work_type(w) {
unmapped_count += 1;
assert!(
rendered.contains("/* TODO:"),
"{w:?} classifies as unmapped — render_work_type \
must dispatch through render_work_type_todo: {rendered}",
);
} else {
runnable_count += 1;
assert!(
!rendered.contains("/* TODO:"),
"{w:?} classifies as runnable — render_work_type \
must produce a builder call without a TODO \
placeholder: {rendered}",
);
}
}
assert!(runnable_count > 0, "runnable partition must not be empty",);
assert!(unmapped_count > 0, "unmapped partition must not be empty",);
}
#[test]
fn is_runnable_unmapped_work_type_via_direct_config() {
let mut spec = ReproducerSpec::default();
spec.config.work_type = WorkType::CacheYield {
size_kib: 256,
stride: 64,
};
assert!(
spec.notes.is_empty(),
"fixture must not add notes; got {:?}",
spec.notes,
);
assert!(
!spec.is_runnable(),
"spec with unmapped work_type must NOT be runnable, even \
without projection notes; config.work_type: {:?}",
spec.config.work_type,
);
assert_eq!(
spec.unresolved_count(),
0,
"unresolved_count covers typed unresolved notes only; got {}",
spec.unresolved_count(),
);
}
#[test]
fn record_work_type_emits_note_for_unmapped_projection() {
let mut spec = ReproducerSpec::default();
record_work_type(WorkType::ForkExit, &mut spec);
assert!(
matches!(spec.config.work_type, WorkType::ForkExit),
"record_work_type must assign the variant to spec.config.work_type",
);
assert!(
spec.notes
.iter()
.any(|n| matches!(n, ReproducerNote::UnmappedWorkType(_))),
"unmapped projection must push a ReproducerNote::UnmappedWorkType: {:?}",
spec.notes,
);
assert_eq!(
spec.unresolved_count(),
1,
"unresolved_count must include the UnmappedWorkType note",
);
assert!(
!spec.is_runnable(),
"spec with TODO note + unmapped work_type must be NOT runnable",
);
}
#[test]
fn record_work_type_does_not_emit_note_for_runnable_projection() {
let mut spec = ReproducerSpec::default();
record_work_type(WorkType::SpinWait, &mut spec);
assert!(
spec.notes.is_empty(),
"runnable projection must NOT push an unmapped note: {:?}",
spec.notes,
);
assert_eq!(spec.unresolved_count(), 0);
}
#[test]
fn is_runnable_combines_work_type_and_affinity_signals() {
let mut cap = DebugCapture::default();
cap.fingerprint.affinity_hints = vec![AffinityHint::Exact { cpus: Vec::new() }];
let mut spec = generate_spec(&cap);
record_work_type(WorkType::ForkExit, &mut spec);
assert!(!spec.is_runnable());
assert_eq!(
spec.unresolved_count(),
2,
"expected 2 unresolved notes (1 affinity + 1 work-type), got {}: {:?}",
spec.unresolved_count(),
spec.notes,
);
}
#[test]
fn reproducer_note_wire_format_is_snake_case() {
let cases: &[(ReproducerNote, &str)] = &[
(
ReproducerNote::Informational("info".into()),
"informational",
),
(ReproducerNote::Resolved("res".into()), "resolved"),
(
ReproducerNote::UnresolvedAffinity("ua".into()),
"unresolved_affinity",
),
(
ReproducerNote::UnmappedWorkType("uwt".into()),
"unmapped_work_type",
),
];
for (note, expected_kind) in cases {
let json =
serde_json::to_string(note).expect("ReproducerNote must serialize via the derive impl");
let kind_pin = format!(r#""kind":"{expected_kind}""#);
assert!(
json.contains(&kind_pin),
"wire format must encode kind={expected_kind:?} (snake_case) — \
a regression that drops `#[serde(rename_all = \"snake_case\")]` \
from the enum would revert to PascalCase. note={note:?}, json={json}",
);
let round_tripped: ReproducerNote = serde_json::from_str(&json)
.expect("ReproducerNote must deserialize from its own serialized form");
assert_eq!(
std::mem::discriminant(note),
std::mem::discriminant(&round_tripped),
"roundtrip must preserve the variant — asymmetric rename \
between Serialize and Deserialize would surface here. \
sent={note:?}, got={round_tripped:?}",
);
assert_eq!(
note.message(),
round_tripped.message(),
"roundtrip must preserve the message payload",
);
}
}
#[test]
fn render_run_file_source_compiles_via_rustc() {
let mut cap = DebugCapture::default();
cap.fingerprint.workload_groups = vec![WorkloadGroupHint {
cgroup_path: "/system.slice/foo.service".into(),
thread_count: 16,
cpu_time_fraction: 0.65,
wakeups_per_sec: 850.0,
}];
cap.fingerprint.affinity_hints = vec![AffinityHint::Exact {
cpus: vec![0, 1, 4, 5],
}];
cap.fingerprint.work_type_hints = vec![WorkTypeHint::Bursty {
burst_duration: Duration::from_millis(7),
sleep_duration: Duration::from_millis(43),
}];
cap.fingerprint.cgroup_hints = vec![CgroupHint {
path: "/system.slice/foo.service".into(),
cpu_weight: Some(150),
memory_max_bytes: Some(4 * 1024 * 1024 * 1024),
cpuset_cpus: vec![0, 1, 4, 5],
cpu_max_quota_us: Some(50_000),
}];
cap.fingerprint.sched_policy_hints = vec![SchedPolicyHint::Deadline {
runtime_ns: 1_000_000,
deadline_ns: 5_000_000,
period_ns: 10_000_000,
}];
cap.fingerprint.gaps = vec!["sample window had 2 dropouts".into()];
let spec = generate_spec(&cap);
let rendered = render_run_file_source(&spec, "compile_check_repro");
let stub = r#"
#[allow(dead_code, unused_variables, unused_imports)]
mod ktstr { pub mod workload {
use std::collections::BTreeSet;
use std::time::Duration;
pub struct WorkloadConfig;
impl WorkloadConfig {
pub fn default() -> Self { Self }
pub fn workers(self, _: usize) -> Self { self }
pub fn affinity(self, _: AffinityIntent) -> Self { self }
pub fn work_type(self, _: WorkType) -> Self { self }
pub fn sched_policy(self, _: SchedPolicy) -> Self { self }
pub fn nice(self, _: i32) -> Self { self }
}
pub enum AffinityIntent {
Inherit,
SingleCpu,
LlcAligned,
CrossCgroup,
SmtSiblingPair,
RandomSubset { from: BTreeSet<usize>, count: usize },
Exact(BTreeSet<usize>),
}
pub enum WorkType {
SpinWait,
YieldHeavy,
Mixed,
IoSyncWrite,
IoRandRead,
IoConvoy,
Bursty { burst_duration: Duration, sleep_duration: Duration },
PipeIo { burst_iters: u64 },
FutexPingPong { spin_iters: u64 },
CachePressure { size_kib: usize, stride: usize },
}
pub enum SchedPolicy {
Normal,
Batch,
Idle,
Fifo(u32),
RoundRobin(u32),
Deadline { runtime: Duration, deadline: Duration, period: Duration },
}
}}
"#;
let combined = format!("{stub}\n{rendered}");
use std::io::Write as _;
let mut tmp = tempfile::Builder::new()
.prefix("ktstr_reproducer_compile_check_")
.suffix(".rs")
.tempfile()
.expect("create tempfile for rendered source");
tmp.write_all(combined.as_bytes())
.expect("write rendered source");
tmp.flush().expect("flush rendered source");
let out_dir = tempfile::TempDir::new().expect("rustc out tempdir");
let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into());
let output = std::process::Command::new(&rustc)
.arg("--edition")
.arg("2021")
.arg("--crate-type")
.arg("lib")
.arg("--out-dir")
.arg(out_dir.path())
.arg(tmp.path())
.output()
.unwrap_or_else(|e| {
panic!(
"render_run_file_source_compiles_via_rustc requires rustc \
(resolved via $RUSTC, then $PATH) — failed to spawn {rustc:?}: {e}. \
Cargo sets $RUSTC for cargo-test / cargo-nextest invocations; if \
you are running this test outside of cargo, ensure rustc is on \
$PATH or set $RUSTC explicitly. The test does NOT silently skip \
when rustc is missing — silent-skip would falsely report green \
when the rendered-source compile-check never ran.",
)
});
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!(
"rustc rejected rendered source\n\
---- rustc stderr ----\n\
{stderr}\n\
---- combined source ----\n\
{combined}",
);
}
}