use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SchedulerKind {
#[default]
Eevdf,
Discover,
Path,
KernelBuiltin,
}
impl std::fmt::Display for SchedulerKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
SchedulerKind::Eevdf => "eevdf",
SchedulerKind::Discover => "discover",
SchedulerKind::Path => "path",
SchedulerKind::KernelBuiltin => "kernel_builtin",
};
f.write_str(s)
}
}
impl From<&crate::test_support::SchedulerSpec> for SchedulerKind {
fn from(spec: &crate::test_support::SchedulerSpec) -> Self {
match spec {
crate::test_support::SchedulerSpec::Eevdf => SchedulerKind::Eevdf,
crate::test_support::SchedulerSpec::Discover(_) => SchedulerKind::Discover,
crate::test_support::SchedulerSpec::Path(_) => SchedulerKind::Path,
crate::test_support::SchedulerSpec::KernelBuiltin { .. } => {
SchedulerKind::KernelBuiltin
}
}
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShellTestDescriptor {
#[serde(default)]
pub numa_nodes: u32,
#[serde(default)]
pub llcs: u32,
#[serde(default)]
pub cores: u32,
#[serde(default)]
pub threads: u32,
#[serde(default)]
pub memory_mib: u32,
#[serde(default)]
pub wprof: bool,
#[serde(default)]
pub extra_include_files: Vec<String>,
#[serde(default)]
pub scheduler_name: String,
#[serde(default)]
pub scheduler_kind: SchedulerKind,
#[serde(default)]
pub wprof_args: Option<String>,
#[serde(default)]
pub performance_mode: bool,
#[serde(default)]
pub scheduler_enable_cmds: Vec<String>,
#[serde(default)]
pub scheduler_disable_cmds: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
fn fully_populated() -> ShellTestDescriptor {
ShellTestDescriptor {
numa_nodes: 2,
llcs: 4,
cores: 6,
threads: 2,
memory_mib: 4096,
wprof: true,
extra_include_files: vec!["a:/x".to_string(), "b:/y".to_string()],
scheduler_name: "scx_test".to_string(),
scheduler_kind: SchedulerKind::KernelBuiltin,
wprof_args: Some("-d 2000 -e sched,irq".to_string()),
performance_mode: true,
scheduler_enable_cmds: vec!["echo on > /sys/kernel/debug/foo".to_string()],
scheduler_disable_cmds: vec!["echo off > /sys/kernel/debug/foo".to_string()],
}
}
#[test]
fn roundtrip_preserves_every_field() {
let original = fully_populated();
let json = serde_json::to_string(&original).expect("serialize fully-populated descriptor");
let parsed: ShellTestDescriptor =
serde_json::from_str(&json).expect("deserialize fully-populated descriptor");
assert_eq!(parsed, original);
}
#[test]
fn missing_new_fields_default_to_empty() {
let legacy_json = r#"{
"numa_nodes": 1,
"llcs": 1,
"cores": 2,
"threads": 1,
"memory_mib": 1024,
"wprof": false,
"extra_include_files": [],
"scheduler_name": "scx_legacy",
"scheduler_kind": "discover"
}"#;
let parsed: ShellTestDescriptor =
serde_json::from_str(legacy_json).expect("legacy JSON missing new fields must parse");
assert_eq!(parsed.wprof_args, None);
assert!(!parsed.performance_mode);
assert!(parsed.scheduler_enable_cmds.is_empty());
assert!(parsed.scheduler_disable_cmds.is_empty());
assert_eq!(parsed.numa_nodes, 1);
assert_eq!(parsed.scheduler_name, "scx_legacy");
assert_eq!(parsed.scheduler_kind, SchedulerKind::Discover);
}
#[test]
fn scheduler_kind_serde_each_variant_roundtrips_snake_case() {
for (variant, wire) in [
(SchedulerKind::Eevdf, "\"eevdf\""),
(SchedulerKind::Discover, "\"discover\""),
(SchedulerKind::Path, "\"path\""),
(SchedulerKind::KernelBuiltin, "\"kernel_builtin\""),
] {
let json = serde_json::to_string(&variant).expect("serialize");
assert_eq!(json, wire, "serialize mismatch for {variant:?}");
let back: SchedulerKind = serde_json::from_str(wire).expect("deserialize");
assert_eq!(back, variant, "roundtrip mismatch for {variant:?}");
}
}
#[test]
fn scheduler_kind_display_matches_serde_snake_case() {
for variant in [
SchedulerKind::Eevdf,
SchedulerKind::Discover,
SchedulerKind::Path,
SchedulerKind::KernelBuiltin,
] {
let json = serde_json::to_string(&variant).expect("serialize");
let unquoted = json.trim_matches('"');
assert_eq!(
variant.to_string(),
unquoted,
"Display must equal serde wire form for {variant:?}",
);
}
}
#[test]
fn scheduler_kind_from_scheduler_spec_per_variant() {
use crate::test_support::SchedulerSpec;
assert_eq!(
SchedulerKind::from(&SchedulerSpec::Eevdf),
SchedulerKind::Eevdf,
);
assert_eq!(
SchedulerKind::from(&SchedulerSpec::Discover("scx_test")),
SchedulerKind::Discover,
);
assert_eq!(
SchedulerKind::from(&SchedulerSpec::Path("/bin/scx_test")),
SchedulerKind::Path,
);
assert_eq!(
SchedulerKind::from(&SchedulerSpec::KernelBuiltin {
enable: &[],
disable: &[],
}),
SchedulerKind::KernelBuiltin,
);
}
#[test]
fn scheduler_kind_unknown_variant_fails_deserialize() {
let r: Result<SchedulerKind, _> = serde_json::from_str("\"rust\"");
assert!(r.is_err(), "unknown variant 'rust' must reject; got {r:?}",);
}
#[test]
fn missing_every_field_yields_full_defaults() {
let empty_json = "{}";
let parsed: ShellTestDescriptor =
serde_json::from_str(empty_json).expect("empty JSON must parse with all defaults");
assert_eq!(parsed.numa_nodes, 0);
assert_eq!(parsed.scheduler_name, "");
assert_eq!(parsed.scheduler_kind, SchedulerKind::Eevdf);
assert!(parsed.extra_include_files.is_empty());
assert_eq!(parsed.wprof_args, None);
assert!(!parsed.performance_mode);
assert!(parsed.scheduler_enable_cmds.is_empty());
assert!(parsed.scheduler_disable_cmds.is_empty());
}
}