use crate::test_support::KtstrTestEntry;
use crate::vmm::topology::Topology;
pub(crate) struct TestCandidate {
pub name: String,
pub features: u64,
pub estimated_secs: f64,
}
const SCHED_SHIFT: u32 = 0;
const CPU_BUCKET_SHIFT: u32 = 4;
const LLC_BUCKET_SHIFT: u32 = 9;
const SMT_SHIFT: u32 = 14;
const PERF_MODE_SHIFT: u32 = 15;
const HOST_ONLY_SHIFT: u32 = 16;
const EXPECT_ERR_SHIFT: u32 = 17;
const DURATION_SHIFT: u32 = 18;
const GAUNTLET_SHIFT: u32 = 22;
const NAME_HASH_SHIFT: u32 = 23;
const NUMA_BUCKET_SHIFT: u32 = 27;
fn djb2_hash(name: &str) -> u32 {
let mut h: u32 = 5381;
for b in name.bytes() {
h = h.wrapping_mul(33).wrapping_add(b as u32);
}
h
}
#[cfg(test)]
const fn sched_hash_bits() -> u32 {
4
}
#[cfg(test)]
const fn name_hash_bits() -> u32 {
4
}
fn cpu_bucket(total_cpus: u32) -> u64 {
match total_cpus {
0..=8 => 1 << 0,
9..=16 => 1 << 1,
17..=64 => 1 << 2,
65..=128 => 1 << 3,
_ => 1 << 4,
}
}
#[cfg(test)]
const fn cpu_bucket_bits() -> u32 {
5
}
fn llc_bucket(num_llcs: u32) -> u64 {
match num_llcs {
0..=1 => 1 << 0,
2 => 1 << 1,
3..=4 => 1 << 2,
5..=8 => 1 << 3,
_ => 1 << 4,
}
}
#[cfg(test)]
const fn llc_bucket_bits() -> u32 {
5
}
fn duration_bucket(duration_secs: u64) -> u64 {
match duration_secs {
0..=2 => 1 << 0,
3..=10 => 1 << 1,
_ => 1 << 2,
}
}
#[cfg(test)]
const fn duration_bucket_bits() -> u32 {
3
}
fn numa_bucket(numa_nodes: u32) -> u64 {
match numa_nodes {
0..=1 => 1 << 0,
2 => 1 << 1,
3..=4 => 1 << 2,
_ => 1 << 3,
}
}
#[cfg(test)]
const fn numa_bucket_bits() -> u32 {
4
}
pub(crate) fn extract_features(
entry: &KtstrTestEntry,
topo: &Topology,
is_gauntlet: bool,
test_name: &str,
) -> u64 {
let mut bits = 0u64;
bits |= (1u64 << (djb2_hash(entry.scheduler.name) % 4)) << SCHED_SHIFT;
bits |= cpu_bucket(topo.total_cpus()) << CPU_BUCKET_SHIFT;
bits |= llc_bucket(topo.num_llcs()) << LLC_BUCKET_SHIFT;
if topo.threads_per_core > 1 {
bits |= 1 << SMT_SHIFT;
}
bits |= numa_bucket(topo.numa_nodes) << NUMA_BUCKET_SHIFT;
if entry.performance_mode {
bits |= 1 << PERF_MODE_SHIFT;
}
if entry.host_only {
bits |= 1 << HOST_ONLY_SHIFT;
}
if entry.expect_err {
bits |= 1 << EXPECT_ERR_SHIFT;
}
bits |= duration_bucket(entry.duration.as_secs()) << DURATION_SHIFT;
if is_gauntlet {
bits |= 1 << GAUNTLET_SHIFT;
}
bits |= (1u64 << (djb2_hash(test_name) % 4)) << NAME_HASH_SHIFT;
bits
}
pub(crate) fn estimate_duration(entry: &KtstrTestEntry, topo: &Topology) -> f64 {
let duration_secs = entry.duration.as_secs();
if entry.host_only {
return (duration_secs + 2) as f64;
}
let cpus = topo.total_cpus() as u64;
let boot_overhead = 10 + cpus.saturating_sub(16) / 10;
let perf_overhead: u64 = if entry.performance_mode { 3 } else { 0 };
(boot_overhead + duration_secs + 2 + perf_overhead) as f64
}
pub(crate) fn select(candidates: &[TestCandidate], budget_secs: f64) -> Vec<usize> {
if candidates.is_empty() || budget_secs <= 0.0 {
return Vec::new();
}
let n = candidates.len();
let mut selected = Vec::new();
let mut used = vec![false; n];
let mut covered: u64 = 0;
let mut remaining_budget = budget_secs;
loop {
let mut best_idx: Option<usize> = None;
let mut best_ratio: f64 = 0.0;
let mut best_name: &str = "";
for (i, c) in candidates.iter().enumerate() {
if used[i] || c.estimated_secs > remaining_budget {
continue;
}
let marginal = (c.features & !covered).count_ones() as f64;
if marginal == 0.0 {
continue;
}
let ratio = if c.estimated_secs > 0.0 {
marginal / c.estimated_secs
} else {
marginal * 1e6 };
let better = ratio > best_ratio || (ratio == best_ratio && c.name.as_str() < best_name);
if better {
best_ratio = ratio;
best_idx = Some(i);
best_name = &c.name;
}
}
match best_idx {
Some(i) => {
selected.push(i);
used[i] = true;
covered |= candidates[i].features;
remaining_budget -= candidates[i].estimated_secs;
}
None => break,
}
}
selected.sort_unstable();
selected
}
pub(crate) struct SelectionStats {
pub selected: usize,
pub total: usize,
pub budget_used: f64,
pub budget_total: f64,
pub bits_covered: u32,
pub bits_possible: u32,
}
pub(crate) fn selection_stats(
candidates: &[TestCandidate],
selected: &[usize],
budget_secs: f64,
) -> SelectionStats {
let mut covered: u64 = 0;
let mut budget_used = 0.0;
for &i in selected {
covered |= candidates[i].features;
budget_used += candidates[i].estimated_secs;
}
let mut all_features: u64 = 0;
for c in candidates {
all_features |= c.features;
}
SelectionStats {
selected: selected.len(),
total: candidates.len(),
budget_used,
budget_total: budget_secs,
bits_covered: covered.count_ones(),
bits_possible: all_features.count_ones(),
}
}
#[cfg(test)]
mod tests {
use super::*;
include!(concat!(env!("OUT_DIR"), "/shift_registry.rs"));
#[test]
fn cpu_bucket_one_hot() {
assert_eq!(cpu_bucket(1), 1 << 0);
assert_eq!(cpu_bucket(8), 1 << 0);
assert_eq!(cpu_bucket(9), 1 << 1);
assert_eq!(cpu_bucket(16), 1 << 1);
assert_eq!(cpu_bucket(17), 1 << 2);
assert_eq!(cpu_bucket(64), 1 << 2);
assert_eq!(cpu_bucket(65), 1 << 3);
assert_eq!(cpu_bucket(128), 1 << 3);
assert_eq!(cpu_bucket(129), 1 << 4);
assert_eq!(cpu_bucket(252), 1 << 4);
}
#[test]
fn cpu_bucket_no_shared_bits() {
let vals = [
cpu_bucket(4),
cpu_bucket(12),
cpu_bucket(32),
cpu_bucket(96),
cpu_bucket(200),
];
for (i, &a) in vals.iter().enumerate() {
assert_eq!(a.count_ones(), 1, "bucket {i} not one-hot: {a:#b}");
for &b in &vals[i + 1..] {
assert_eq!(a & b, 0, "buckets share bits: {a:#b} & {b:#b}");
}
}
}
#[test]
fn llc_bucket_one_hot() {
assert_eq!(llc_bucket(1), 1 << 0);
assert_eq!(llc_bucket(2), 1 << 1);
assert_eq!(llc_bucket(3), 1 << 2);
assert_eq!(llc_bucket(4), 1 << 2);
assert_eq!(llc_bucket(5), 1 << 3);
assert_eq!(llc_bucket(8), 1 << 3);
assert_eq!(llc_bucket(9), 1 << 4);
assert_eq!(llc_bucket(15), 1 << 4);
}
#[test]
fn llc_bucket_no_shared_bits() {
let vals = [
llc_bucket(1),
llc_bucket(2),
llc_bucket(3),
llc_bucket(7),
llc_bucket(10),
];
for (i, &a) in vals.iter().enumerate() {
assert_eq!(a.count_ones(), 1);
for &b in &vals[i + 1..] {
assert_eq!(a & b, 0);
}
}
}
#[test]
fn duration_bucket_one_hot() {
assert_eq!(duration_bucket(0), 1 << 0);
assert_eq!(duration_bucket(2), 1 << 0);
assert_eq!(duration_bucket(3), 1 << 1);
assert_eq!(duration_bucket(10), 1 << 1);
assert_eq!(duration_bucket(11), 1 << 2);
}
#[test]
fn duration_bucket_no_shared_bits() {
let vals = [duration_bucket(1), duration_bucket(5), duration_bucket(20)];
for (i, &a) in vals.iter().enumerate() {
assert_eq!(a.count_ones(), 1);
for &b in &vals[i + 1..] {
assert_eq!(a & b, 0);
}
}
}
#[test]
fn numa_bucket_one_hot() {
assert_eq!(numa_bucket(0), 1 << 0);
assert_eq!(numa_bucket(1), 1 << 0);
assert_eq!(numa_bucket(2), 1 << 1);
assert_eq!(numa_bucket(3), 1 << 2);
assert_eq!(numa_bucket(4), 1 << 2);
assert_eq!(numa_bucket(5), 1 << 3);
assert_eq!(numa_bucket(8), 1 << 3);
}
#[test]
fn numa_bucket_no_shared_bits() {
let vals = [
numa_bucket(1),
numa_bucket(2),
numa_bucket(3),
numa_bucket(5),
];
for (i, &a) in vals.iter().enumerate() {
assert_eq!(a.count_ones(), 1, "bucket {i} not one-hot: {a:#b}");
for &b in &vals[i + 1..] {
assert_eq!(a & b, 0, "buckets share bits: {a:#b} & {b:#b}");
}
}
}
#[test]
fn extract_features_numa_differentiation() {
let entry = KtstrTestEntry::DEFAULT;
let topo1 = Topology {
llcs: 4,
cores_per_llc: 4,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let topo2 = Topology {
llcs: 4,
cores_per_llc: 4,
threads_per_core: 1,
numa_nodes: 2,
nodes: None,
distances: None,
};
let f1 = extract_features(&entry, &topo1, false, "numa_test");
let f2 = extract_features(&entry, &topo2, false, "numa_test");
assert_ne!(f1, f2);
let numa_mask = 0xFu64 << NUMA_BUCKET_SHIFT;
assert_ne!(f1 & numa_mask, f2 & numa_mask);
}
#[test]
fn extract_features_base_test() {
use crate::test_support::KtstrTestEntry;
let entry = KtstrTestEntry::DEFAULT;
let topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let features = extract_features(&entry, &topo, false, "basic_test");
assert_eq!(features & (1 << GAUNTLET_SHIFT), 0);
assert_eq!(features & (1 << SMT_SHIFT), 0);
assert_eq!(features & (1 << PERF_MODE_SHIFT), 0);
}
#[test]
fn extract_features_smt_set() {
let entry = KtstrTestEntry::DEFAULT;
let topo = Topology {
llcs: 2,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let features = extract_features(&entry, &topo, false, "smt_test");
assert_ne!(features & (1 << SMT_SHIFT), 0);
}
#[test]
fn extract_features_gauntlet() {
let entry = KtstrTestEntry::DEFAULT;
let topo = Topology {
llcs: 4,
cores_per_llc: 4,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let features = extract_features(&entry, &topo, true, "gauntlet_test");
assert_ne!(features & (1 << GAUNTLET_SHIFT), 0);
}
#[test]
fn extract_features_bit_shifts_orthogonal() {
assert_eq!(SMT_SHIFT, 14, "SMT_SHIFT pinned");
assert_eq!(PERF_MODE_SHIFT, 15, "PERF_MODE_SHIFT pinned");
assert_eq!(HOST_ONLY_SHIFT, 16, "HOST_ONLY_SHIFT pinned");
assert_eq!(EXPECT_ERR_SHIFT, 17, "EXPECT_ERR_SHIFT pinned");
assert_eq!(GAUNTLET_SHIFT, 22, "GAUNTLET_SHIFT pinned");
let plain_topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
let smt_topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
let one_bit_shifts: &[(u32, &str)] = &[
(SMT_SHIFT, "SMT"),
(PERF_MODE_SHIFT, "PERF_MODE"),
(HOST_ONLY_SHIFT, "HOST_ONLY"),
(EXPECT_ERR_SHIFT, "EXPECT_ERR"),
(GAUNTLET_SHIFT, "GAUNTLET"),
];
let assert_only = |features: u64, trigger_label: &str| {
let trigger_shift = one_bit_shifts
.iter()
.find(|(_, name)| *name == trigger_label)
.map(|(shift, _)| *shift)
.expect("trigger_label must be present in one_bit_shifts");
assert_ne!(
features & (1 << trigger_shift),
0,
"{trigger_label}: triggered bit must be set",
);
for &(other_shift, other_name) in one_bit_shifts {
if other_name == trigger_label {
continue;
}
assert_eq!(
features & (1 << other_shift),
0,
"{trigger_label} trigger must leave {other_name} bit \
(shift {other_shift}) clear — collision indicates \
a shift-overlap regression",
);
}
};
let smt_f = extract_features(&KtstrTestEntry::DEFAULT, &smt_topo, false, "smt_test");
assert_only(smt_f, "SMT");
let perf_entry = KtstrTestEntry {
performance_mode: true,
..KtstrTestEntry::DEFAULT
};
let perf_f = extract_features(&perf_entry, &plain_topo, false, "perf_test");
assert_only(perf_f, "PERF_MODE");
let host_only_entry = KtstrTestEntry {
host_only: true,
..KtstrTestEntry::DEFAULT
};
let host_only_f = extract_features(&host_only_entry, &plain_topo, false, "host_test");
assert_only(host_only_f, "HOST_ONLY");
let expect_err_entry = KtstrTestEntry {
expect_err: true,
..KtstrTestEntry::DEFAULT
};
let expect_err_f =
extract_features(&expect_err_entry, &plain_topo, false, "expect_err_test");
assert_only(expect_err_f, "EXPECT_ERR");
let gauntlet_f =
extract_features(&KtstrTestEntry::DEFAULT, &plain_topo, true, "gauntlet_test");
assert_only(gauntlet_f, "GAUNTLET");
}
#[test]
fn extract_features_one_bit_shifts_outside_multi_bit_ranges() {
let multi_bit_ranges: &[(u32, u32, &str)] = &[
(SCHED_SHIFT, SCHED_SHIFT + sched_hash_bits() - 1, "SCHED"),
(
CPU_BUCKET_SHIFT,
CPU_BUCKET_SHIFT + cpu_bucket_bits() - 1,
"CPU_BUCKET",
),
(
LLC_BUCKET_SHIFT,
LLC_BUCKET_SHIFT + llc_bucket_bits() - 1,
"LLC_BUCKET",
),
(
DURATION_SHIFT,
DURATION_SHIFT + duration_bucket_bits() - 1,
"DURATION",
),
(
NAME_HASH_SHIFT,
NAME_HASH_SHIFT + name_hash_bits() - 1,
"NAME_HASH",
),
(
NUMA_BUCKET_SHIFT,
NUMA_BUCKET_SHIFT + numa_bucket_bits() - 1,
"NUMA_BUCKET",
),
];
let one_bit_shifts: &[(u32, &str)] = &[
(SMT_SHIFT, "SMT"),
(PERF_MODE_SHIFT, "PERF_MODE"),
(HOST_ONLY_SHIFT, "HOST_ONLY"),
(EXPECT_ERR_SHIFT, "EXPECT_ERR"),
(GAUNTLET_SHIFT, "GAUNTLET"),
];
for &(shift, name) in one_bit_shifts {
for &(start, end, range_name) in multi_bit_ranges {
assert!(
shift < start || shift > end,
"{name} shift={shift} falls inside multi-bit field {range_name} \
range [{start}..={end}] — overlap would let {range_name} \
silently set the {name} bit"
);
}
}
}
#[test]
fn all_shifts_classified_in_exactly_one_enumeration() {
use std::collections::HashSet;
let one_bit_values: HashSet<u32> = [
SMT_SHIFT,
PERF_MODE_SHIFT,
HOST_ONLY_SHIFT,
EXPECT_ERR_SHIFT,
GAUNTLET_SHIFT,
]
.into_iter()
.collect();
let multi_bit_values: HashSet<u32> = [
SCHED_SHIFT,
CPU_BUCKET_SHIFT,
LLC_BUCKET_SHIFT,
DURATION_SHIFT,
NAME_HASH_SHIFT,
NUMA_BUCKET_SHIFT,
]
.into_iter()
.collect();
let classified: HashSet<u32> = one_bit_values.union(&multi_bit_values).copied().collect();
let registry: HashSet<u32> = ALL_SHIFTS.iter().map(|(v, _)| *v).collect();
let unclassified: Vec<(u32, &str)> = ALL_SHIFTS
.iter()
.filter(|(v, _)| !classified.contains(v))
.copied()
.collect();
let phantom_one_bit: Vec<u32> = one_bit_values.difference(®istry).copied().collect();
let phantom_multi_bit: Vec<u32> = multi_bit_values.difference(®istry).copied().collect();
let overlap: Vec<u32> = one_bit_values
.intersection(&multi_bit_values)
.copied()
.collect();
assert!(
unclassified.is_empty(),
"unclassified *_SHIFT constants (present in build-script \
scan, absent from both test enumerations): {unclassified:?}. \
Add to extract_features_bit_shifts_orthogonal's one_bit_shifts \
(if a single-bit flag) or extract_features_one_bit_shifts_outside_multi_bit_ranges's \
multi_bit_ranges (if a multi-bit field).",
);
assert!(
phantom_one_bit.is_empty() && phantom_multi_bit.is_empty(),
"test enumerations reference shift values that no \
`const *_SHIFT: u32 = N;` in src/budget.rs declares — stale \
enumeration entries. one_bit_shifts has phantom values \
{phantom_one_bit:?} (remove from \
extract_features_bit_shifts_orthogonal); multi_bit_ranges \
has phantom values {phantom_multi_bit:?} (remove from \
extract_features_one_bit_shifts_outside_multi_bit_ranges).",
);
assert!(
overlap.is_empty(),
"*_SHIFT constants classified as BOTH one-bit and multi-bit: \
{overlap:?} — each shift must belong to exactly one \
enumeration. Remove from either \
extract_features_bit_shifts_orthogonal's one_bit_shifts \
(if it is a multi-bit field) or \
extract_features_one_bit_shifts_outside_multi_bit_ranges's \
multi_bit_ranges (if it is a single-bit flag).",
);
}
#[test]
fn cpu_bucket_fits_advertised_width() {
let max = [0u32, 8, 9, 16, 17, 64, 65, 128, 129, u32::MAX]
.iter()
.map(|&x| cpu_bucket(x))
.max()
.expect("non-empty");
let bits_used = 64 - max.leading_zeros();
assert!(
bits_used <= cpu_bucket_bits(),
"cpu_bucket max output {max:#b} uses {bits_used} bits but \
advertised {} bits — grow cpu_bucket_bits() or shrink the bucket",
cpu_bucket_bits(),
);
}
#[test]
fn llc_bucket_fits_advertised_width() {
let max = [0u32, 1, 2, 3, 4, 5, 8, 9, u32::MAX]
.iter()
.map(|&x| llc_bucket(x))
.max()
.expect("non-empty");
let bits_used = 64 - max.leading_zeros();
assert!(
bits_used <= llc_bucket_bits(),
"llc_bucket max output {max:#b} uses {bits_used} bits but \
advertised {} bits — grow llc_bucket_bits() or shrink the bucket",
llc_bucket_bits(),
);
}
#[test]
fn duration_bucket_fits_advertised_width() {
let max = [0u64, 2, 3, 10, 11, u64::MAX]
.iter()
.map(|&x| duration_bucket(x))
.max()
.expect("non-empty");
let bits_used = 64 - max.leading_zeros();
assert!(
bits_used <= duration_bucket_bits(),
"duration_bucket max output {max:#b} uses {bits_used} bits but \
advertised {} bits — grow duration_bucket_bits() or shrink the bucket",
duration_bucket_bits(),
);
}
#[test]
fn numa_bucket_fits_advertised_width() {
let max = [0u32, 1, 2, 3, 4, 5, u32::MAX]
.iter()
.map(|&x| numa_bucket(x))
.max()
.expect("non-empty");
let bits_used = 64 - max.leading_zeros();
assert!(
bits_used <= numa_bucket_bits(),
"numa_bucket max output {max:#b} uses {bits_used} bits but \
advertised {} bits — grow numa_bucket_bits() or shrink the bucket",
numa_bucket_bits(),
);
}
#[test]
fn hash_bits_match_one_hot_shape() {
let max_hash_value = (0u32..=3).map(|h| 1u64 << h).max().expect("non-empty");
let bits_used = 64 - max_hash_value.leading_zeros();
assert!(
bits_used <= sched_hash_bits(),
"sched_hash max output {max_hash_value:#b} uses {bits_used} bits but \
advertised {} bits",
sched_hash_bits(),
);
assert!(
bits_used <= name_hash_bits(),
"name_hash max output {max_hash_value:#b} uses {bits_used} bits but \
advertised {} bits",
name_hash_bits(),
);
}
#[test]
fn all_shift_values_unique() {
use std::collections::HashSet;
let distinct: HashSet<u32> = ALL_SHIFTS.iter().map(|(v, _)| *v).collect();
assert_eq!(
distinct.len(),
ALL_SHIFTS.len(),
"duplicate SHIFT values detected — two `const *_SHIFT: u32 = N;` \
declarations in src/budget.rs share the same numeric value. \
ALL_SHIFTS entries: {:?}",
ALL_SHIFTS,
);
}
#[test]
fn estimate_duration_small_topo() {
let entry = KtstrTestEntry {
duration: std::time::Duration::from_secs(2),
..KtstrTestEntry::DEFAULT
};
let topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(estimate_duration(&entry, &topo), 14.0);
}
#[test]
fn estimate_duration_large_topo() {
let entry = KtstrTestEntry {
duration: std::time::Duration::from_secs(5),
..KtstrTestEntry::DEFAULT
};
let topo = Topology {
llcs: 14,
cores_per_llc: 9,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(estimate_duration(&entry, &topo), 40.0);
}
#[test]
fn estimate_duration_performance_mode() {
let entry = KtstrTestEntry {
duration: std::time::Duration::from_secs(2),
performance_mode: true,
..KtstrTestEntry::DEFAULT
};
let topo = Topology {
llcs: 1,
cores_per_llc: 2,
threads_per_core: 1,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(estimate_duration(&entry, &topo), 17.0);
}
#[test]
fn select_empty() {
assert!(select(&[], 100.0).is_empty());
}
#[test]
fn select_zero_budget() {
let candidates = vec![TestCandidate {
name: "t1".into(),
features: 0xFF,
estimated_secs: 10.0,
}];
assert!(select(&candidates, 0.0).is_empty());
}
#[test]
fn select_single_fits() {
let candidates = vec![TestCandidate {
name: "t1".into(),
features: 0xFF,
estimated_secs: 10.0,
}];
let sel = select(&candidates, 20.0);
assert_eq!(sel, vec![0]);
}
#[test]
fn select_single_too_expensive() {
let candidates = vec![TestCandidate {
name: "t1".into(),
features: 0xFF,
estimated_secs: 30.0,
}];
let sel = select(&candidates, 20.0);
assert!(sel.is_empty());
}
#[test]
fn select_prefers_coverage_per_second() {
let candidates = vec![
TestCandidate {
name: "t1".into(),
features: 0b1111,
estimated_secs: 20.0,
},
TestCandidate {
name: "t2".into(),
features: 0b110000,
estimated_secs: 5.0,
},
];
let sel = select(&candidates, 25.0);
assert_eq!(sel, vec![0, 1]); }
#[test]
fn select_budget_constraint() {
let candidates = vec![
TestCandidate {
name: "t1".into(),
features: 0b1111,
estimated_secs: 15.0,
},
TestCandidate {
name: "t2".into(),
features: 0b110000,
estimated_secs: 15.0,
},
];
let sel = select(&candidates, 20.0);
assert_eq!(sel, vec![0]);
}
#[test]
fn select_marginal_coverage_decreases() {
let candidates = vec![
TestCandidate {
name: "t1".into(),
features: 0b1111,
estimated_secs: 5.0,
},
TestCandidate {
name: "t2".into(),
features: 0b1111,
estimated_secs: 5.0,
},
TestCandidate {
name: "t3".into(),
features: 0b110000,
estimated_secs: 5.0,
},
];
let sel = select(&candidates, 100.0);
assert_eq!(sel.len(), 2);
assert!(sel.contains(&2)); }
#[test]
fn select_sorted_output() {
let candidates = vec![
TestCandidate {
name: "t1".into(),
features: 0b01,
estimated_secs: 10.0,
},
TestCandidate {
name: "t2".into(),
features: 0b10,
estimated_secs: 5.0,
},
];
let sel = select(&candidates, 100.0);
assert_eq!(sel, vec![0, 1]);
}
#[test]
fn djb2_hash_one_hot_4() {
for name in &["eevdf", "scx_mitosis", "scx_rusty", "", "x"] {
let one_hot = 1u64 << (djb2_hash(name) % 4);
assert_eq!(one_hot.count_ones(), 1);
assert!(one_hot < 16);
}
}
#[test]
fn djb2_hash_different_names_differ() {
let h1 = djb2_hash("eevdf");
let h2 = djb2_hash("scx_mitosis");
assert_ne!(h1, h2);
}
#[test]
fn select_different_features_both_selected() {
let candidates = vec![
TestCandidate {
name: "sched_a_test1".into(),
features: 0b0001,
estimated_secs: 5.0,
},
TestCandidate {
name: "sched_b_test1".into(),
features: 0b0010 | (1 << 3),
estimated_secs: 5.0,
},
];
let sel = select(&candidates, 100.0);
assert_eq!(sel, vec![0, 1]);
}
#[test]
fn select_tie_broken_by_name() {
let candidates = vec![
TestCandidate {
name: "zzz".into(),
features: 0b1111,
estimated_secs: 10.0,
},
TestCandidate {
name: "aaa".into(),
features: 0b1111,
estimated_secs: 10.0,
},
];
let sel = select(&candidates, 15.0);
assert_eq!(sel, vec![1]); }
#[test]
fn selection_stats_basic() {
let candidates = vec![
TestCandidate {
name: "t1".into(),
features: 0b0011,
estimated_secs: 5.0,
},
TestCandidate {
name: "t2".into(),
features: 0b1100,
estimated_secs: 5.0,
},
TestCandidate {
name: "t3".into(),
features: 0b1111,
estimated_secs: 5.0,
},
];
let sel = vec![0, 1];
let stats = selection_stats(&candidates, &sel, 100.0);
assert_eq!(stats.selected, 2);
assert_eq!(stats.total, 3);
assert_eq!(stats.budget_used, 10.0);
assert_eq!(stats.budget_total, 100.0);
assert_eq!(stats.bits_covered, 4);
assert_eq!(stats.bits_possible, 4);
}
#[test]
fn estimate_duration_host_only() {
let entry = KtstrTestEntry {
duration: std::time::Duration::from_secs(5),
host_only: true,
..KtstrTestEntry::DEFAULT
};
let topo = Topology {
llcs: 14,
cores_per_llc: 9,
threads_per_core: 2,
numa_nodes: 1,
nodes: None,
distances: None,
};
assert_eq!(estimate_duration(&entry, &topo), 7.0);
}
#[test]
fn select_zero_cost_selected_first() {
let candidates = vec![
TestCandidate {
name: "free".into(),
features: 0b0001,
estimated_secs: 0.0,
},
TestCandidate {
name: "expensive".into(),
features: 0b0010,
estimated_secs: 100.0,
},
];
let sel = select(&candidates, 50.0);
assert_eq!(sel, vec![0]);
}
}