use anyhow::Result;
use ktstr::assert::AssertResult;
use ktstr::ktstr_test;
use ktstr::scenario::Ctx;
#[ktstr_test(sockets = 1, cores = 2, threads = 1, memory_mb = 2048)]
fn basic_topology_check(ctx: &Ctx) -> Result<AssertResult> {
let total = ctx.topo.total_cpus();
if total == 0 {
return Ok(AssertResult {
passed: false,
details: vec!["no CPUs detected".into()],
stats: Default::default(),
});
}
Ok(AssertResult::pass())
}
#[ktstr_test]
fn default_attrs_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[cfg(feature = "integration")]
#[ktstr_test(sockets = 1, cores = 1, threads = 1)]
fn resolve_func_ip_known_symbol(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
let ip = ktstr::resolve_func_ip("schedule");
if let Some(addr) = ip
&& addr > 0
{
return Ok(AssertResult::pass());
}
Ok(AssertResult {
passed: false,
details: vec![format!("schedule address: {ip:?}")],
stats: Default::default(),
})
}
#[test]
fn find_registered_tests() {
assert!(
ktstr::test_support::find_test("basic_topology_check").is_some(),
"basic_topology_check should be registered in KTSTR_TESTS"
);
assert!(
ktstr::test_support::find_test("default_attrs_compile").is_some(),
"default_attrs_compile should be registered in KTSTR_TESTS"
);
}
#[test]
fn entry_fields_match_attrs() {
let entry = ktstr::test_support::find_test("basic_topology_check").unwrap();
assert_eq!(entry.topology.sockets, 1);
assert_eq!(entry.topology.cores_per_socket, 2);
assert_eq!(entry.topology.threads_per_core, 1);
assert_eq!(entry.memory_mb, 2048);
}
#[test]
fn entry_default_fields() {
let entry = ktstr::test_support::find_test("default_attrs_compile").unwrap();
assert_eq!(entry.topology.sockets, 1);
assert_eq!(entry.topology.cores_per_socket, 2);
assert_eq!(entry.topology.threads_per_core, 1);
assert_eq!(entry.memory_mb, 2048);
assert!(entry.required_flags.is_empty());
assert!(entry.excluded_flags.is_empty());
assert_eq!(entry.constraints.min_sockets, 1);
assert_eq!(entry.constraints.min_llcs, 1);
assert!(!entry.constraints.requires_smt);
assert_eq!(entry.constraints.min_cpus, 1);
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "flag_attrs_test", topology(1, 2, 1))]
#[allow(dead_code)]
enum FlagAttrsTestFlag {
Borrow,
Rebal,
Steal,
}
#[ktstr_test(
scheduler = FLAG_ATTRS_TEST,
required_flags = ["borrow", "rebal"],
excluded_flags = ["steal"]
)]
fn flags_attrs_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_flags_match_attrs() {
let entry = ktstr::test_support::find_test("flags_attrs_compile").unwrap();
assert_eq!(entry.required_flags, &["borrow", "rebal"]);
assert_eq!(entry.excluded_flags, &["steal"]);
}
#[ktstr_test(
sockets = 2,
cores = 4,
threads = 2,
min_sockets = 2,
min_llcs = 4,
requires_smt = true,
min_cpus = 8
)]
fn topo_constraints_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_topo_constraints_match_attrs() {
let entry = ktstr::test_support::find_test("topo_constraints_compile").unwrap();
assert_eq!(entry.constraints.min_sockets, 2);
assert_eq!(entry.constraints.min_llcs, 4);
assert!(entry.constraints.requires_smt);
assert_eq!(entry.constraints.min_cpus, 8);
}
const TOPO_SCHED: ktstr::test_support::Scheduler =
ktstr::test_support::Scheduler::new("topo_test").topology(2, 3, 1);
#[ktstr_test(scheduler = TOPO_SCHED)]
fn topo_inherit_full(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[ktstr_test(scheduler = TOPO_SCHED, threads = 2)]
fn topo_inherit_partial(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_topo_inherit_full() {
let entry = ktstr::test_support::find_test("topo_inherit_full").unwrap();
assert_eq!(entry.topology.sockets, 2);
assert_eq!(entry.topology.cores_per_socket, 3);
assert_eq!(entry.topology.threads_per_core, 1);
}
#[test]
fn entry_topo_inherit_partial() {
let entry = ktstr::test_support::find_test("topo_inherit_partial").unwrap();
assert_eq!(entry.topology.sockets, 2);
assert_eq!(entry.topology.cores_per_socket, 3);
assert_eq!(entry.topology.threads_per_core, 2);
}
#[ktstr_test(sockets = 1, cores = 2, threads = 1, performance_mode = true)]
fn performance_mode_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_performance_mode_set() {
let entry = ktstr::test_support::find_test("performance_mode_compile").unwrap();
assert!(
entry.performance_mode,
"performance_mode = true must be set in generated entry",
);
}
#[derive(ktstr::Scheduler)]
#[scheduler(
name = "test_derive",
binary = "scx-ktstr",
topology(2, 4, 1),
cgroup_parent = "/test",
sched_args = ["--arg1", "--arg2"]
)]
#[allow(dead_code)]
enum TestDeriveFlag {
#[flag(args = ["--enable-alpha"])]
Alpha,
#[flag(args = ["--enable-beta"], requires = [Alpha])]
Beta,
#[flag(args = ["--enable-gamma-delta"])]
GammaDelta,
}
#[test]
fn derive_scheduler_const_name() {
let _ = &TEST_DERIVE;
assert_eq!(TEST_DERIVE.name, "test_derive");
}
#[test]
fn derive_scheduler_binary() {
assert!(matches!(
TEST_DERIVE.binary,
ktstr::test_support::SchedulerSpec::Name("scx-ktstr")
));
}
#[test]
fn derive_scheduler_topology() {
assert_eq!(TEST_DERIVE.topology.sockets, 2);
assert_eq!(TEST_DERIVE.topology.cores_per_socket, 4);
assert_eq!(TEST_DERIVE.topology.threads_per_core, 1);
}
#[test]
fn derive_scheduler_cgroup_parent() {
assert_eq!(TEST_DERIVE.cgroup_parent, Some("/test"));
}
#[test]
fn derive_scheduler_sched_args() {
assert_eq!(TEST_DERIVE.sched_args, &["--arg1", "--arg2"]);
}
#[test]
fn derive_scheduler_flag_count() {
assert_eq!(TEST_DERIVE.flags.len(), 3);
}
#[test]
fn derive_flag_names() {
assert_eq!(TEST_DERIVE.flags[0].name, "alpha");
assert_eq!(TEST_DERIVE.flags[1].name, "beta");
assert_eq!(TEST_DERIVE.flags[2].name, "gamma-delta");
}
#[test]
fn derive_flag_args() {
assert_eq!(TEST_DERIVE.flags[0].args, &["--enable-alpha"]);
assert_eq!(TEST_DERIVE.flags[1].args, &["--enable-beta"]);
assert_eq!(TEST_DERIVE.flags[2].args, &["--enable-gamma-delta"]);
}
#[test]
fn derive_flag_requires() {
assert!(TEST_DERIVE.flags[0].requires.is_empty());
assert_eq!(TEST_DERIVE.flags[1].requires.len(), 1);
assert_eq!(TEST_DERIVE.flags[1].requires[0].name, "alpha");
assert!(TEST_DERIVE.flags[2].requires.is_empty());
}
#[test]
fn derive_name_constants() {
assert_eq!(TestDeriveFlag::ALPHA, "alpha");
assert_eq!(TestDeriveFlag::BETA, "beta");
assert_eq!(TestDeriveFlag::GAMMA_DELTA, "gamma-delta");
}
#[test]
fn derive_profiles_respect_requires() {
let profiles = TEST_DERIVE.generate_profiles(&[TestDeriveFlag::BETA], &[]);
for p in &profiles {
assert!(
p.flags.contains(&TestDeriveFlag::ALPHA),
"beta requires alpha: {:?}",
p.flags
);
}
}
#[ktstr_test(
scheduler = TEST_DERIVE,
required_flags = [TestDeriveFlag::ALPHA, TestDeriveFlag::BETA],
excluded_flags = [TestDeriveFlag::GAMMA_DELTA]
)]
fn typed_flags_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_typed_flags_match() {
let entry = ktstr::test_support::find_test("typed_flags_compile").unwrap();
assert_eq!(entry.required_flags, &["alpha", "beta"]);
assert_eq!(entry.excluded_flags, &["gamma-delta"]);
}
#[ktstr_test(
scheduler = TEST_DERIVE,
required_flags = ["alpha", TestDeriveFlag::BETA]
)]
fn mixed_flags_compile(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_mixed_flags_match() {
let entry = ktstr::test_support::find_test("mixed_flags_compile").unwrap();
assert_eq!(entry.required_flags, &["alpha", "beta"]);
}
#[ktstr_test(scheduler = TEST_DERIVE)]
fn derive_topo_inherit(ctx: &Ctx) -> Result<AssertResult> {
let _ = ctx;
Ok(AssertResult::pass())
}
#[test]
fn entry_derive_topo_inherit() {
let entry = ktstr::test_support::find_test("derive_topo_inherit").unwrap();
assert_eq!(entry.topology.sockets, 2);
assert_eq!(entry.topology.cores_per_socket, 4);
assert_eq!(entry.topology.threads_per_core, 1);
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "empty_sched", binary = "empty-binary", topology(1, 2, 1))]
#[allow(dead_code)]
enum EmptySchedFlag {}
#[test]
fn derive_empty_enum_const_name() {
assert_eq!(EMPTY_SCHED.name, "empty_sched");
}
#[test]
fn derive_empty_enum_no_flags() {
assert!(EMPTY_SCHED.flags.is_empty());
}
#[test]
fn derive_empty_enum_binary() {
assert!(matches!(
EMPTY_SCHED.binary,
ktstr::test_support::SchedulerSpec::Name("empty-binary")
));
}
#[test]
fn derive_empty_enum_profiles() {
let profiles = EMPTY_SCHED.generate_profiles(&[], &[]);
assert_eq!(profiles.len(), 1);
assert!(profiles[0].flags.is_empty());
assert_eq!(profiles[0].name(), "default");
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "test_flags", topology(1, 2, 1))]
#[allow(dead_code)]
enum TestFlags {
#[flag(args = ["--x"])]
Xray,
}
#[test]
fn derive_flags_suffix_stripping() {
assert_eq!(TEST.name, "test_flags");
assert_eq!(TEST.flags.len(), 1);
assert_eq!(TEST.flags[0].name, "xray");
assert_eq!(TestFlags::XRAY, "xray");
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "plain", topology(1, 2, 1))]
#[allow(dead_code)]
enum PlainSched {
#[flag(args = ["--y"])]
Yankee,
}
#[test]
fn derive_no_suffix_const_name() {
assert_eq!(PLAIN_SCHED.name, "plain");
assert_eq!(PLAIN_SCHED.flags[0].name, "yankee");
assert_eq!(PlainSched::YANKEE, "yankee");
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "bare_variant", topology(1, 2, 1))]
#[allow(dead_code)]
enum BareVariantFlag {
NakedVariant,
#[flag(args = ["--with-args"])]
WithArgs,
}
#[test]
fn derive_bare_variant_empty_args() {
let naked = BARE_VARIANT.flags[0];
assert_eq!(naked.name, "naked-variant");
assert!(naked.args.is_empty());
assert!(naked.requires.is_empty());
}
#[test]
fn derive_bare_variant_other_has_args() {
let with_args = BARE_VARIANT.flags[1];
assert_eq!(with_args.name, "with-args");
assert_eq!(with_args.args, &["--with-args"]);
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "acronym_test", topology(1, 2, 1))]
#[allow(dead_code, clippy::upper_case_acronyms)]
enum AcronymFlag {
#[flag(args = ["--llc"])]
LLC,
#[flag(args = ["--io-heavy"])]
IOHeavy,
}
#[test]
fn derive_acronym_llc() {
assert_eq!(ACRONYM.flags[0].name, "llc");
assert_eq!(ACRONYM.flags[0].args, &["--llc"]);
}
#[test]
fn derive_acronym_io_heavy() {
assert_eq!(ACRONYM.flags[1].name, "io-heavy");
assert_eq!(AcronymFlag::IO_HEAVY, "io-heavy");
}
#[derive(ktstr::Scheduler)]
#[scheduler(name = "minimal")]
#[allow(dead_code)]
enum MinimalFlag {}
#[test]
fn derive_minimal_defaults() {
assert_eq!(MINIMAL.name, "minimal");
assert!(!MINIMAL.binary.has_active_scheduling());
assert!(matches!(
MINIMAL.binary,
ktstr::test_support::SchedulerSpec::None
));
assert_eq!(MINIMAL.topology.sockets, 1);
assert_eq!(MINIMAL.topology.cores_per_socket, 2);
assert_eq!(MINIMAL.topology.threads_per_core, 1);
assert!(MINIMAL.flags.is_empty());
assert!(MINIMAL.sched_args.is_empty());
assert!(MINIMAL.cgroup_parent.is_none());
}
#[ktstr_test(
sockets = 2,
cores = 2,
threads = 1,
memory_mb = 2048,
min_sockets = 2,
min_llcs = 2,
min_cpus = 4
)]
fn topology_matches_vm_spec(ctx: &Ctx) -> Result<AssertResult> {
let total = ctx.topo.total_cpus();
let llcs = ctx.topo.num_llcs();
let mut details = Vec::new();
let mut passed = true;
if total < 4 {
details.push(format!("expected >= 4 CPUs, got {total}"));
passed = false;
}
if llcs < 2 {
details.push(format!("expected >= 2 LLCs, got {llcs}"));
passed = false;
}
if llcs > total {
details.push(format!("LLCs ({llcs}) > CPUs ({total})"));
passed = false;
}
if passed {
Ok(AssertResult::pass())
} else {
Ok(AssertResult {
passed: false,
details,
stats: Default::default(),
})
}
}