use super::super::test_helpers::{EnvVarGuard, lock_env};
use super::*;
use crate::assert::{AssertResult, CgroupStats};
use crate::scenario::Ctx;
use anyhow::Result;
fn find_sidecars_by_prefix(dir: &std::path::Path, prefix: &str) -> Vec<std::path::PathBuf> {
std::fs::read_dir(dir)
.expect("sidecar dir must exist for lookup")
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| {
p.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with(prefix) && n.ends_with(".ktstr.json"))
})
.collect()
}
fn find_single_sidecar_by_prefix(dir: &std::path::Path, prefix: &str) -> std::path::PathBuf {
let paths = find_sidecars_by_prefix(dir, prefix);
assert_eq!(
paths.len(),
1,
"single-variant test must produce exactly one sidecar under \
prefix {prefix:?}; got {paths:?}",
);
paths
.into_iter()
.next()
.expect("length-1 vec yields Some on first next()")
}
#[test]
fn find_sidecars_by_prefix_filters_suffix() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("foo-0001.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("foo-0002.ktstr.json.tmp"), b"{}").unwrap();
std::fs::write(tmp.join("foo-0003.json"), b"{}").unwrap();
std::fs::write(tmp.join("foo-0004.ktstr.txt"), b"{}").unwrap();
let paths = find_sidecars_by_prefix(tmp, "foo-");
assert_eq!(
paths.len(),
1,
"only the .ktstr.json file must match, got {paths:?}",
);
}
#[test]
fn find_sidecars_by_prefix_filters_prefix() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("foo-0001.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("bar-0002.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("foobar-0003.ktstr.json"), b"{}").unwrap();
let paths = find_sidecars_by_prefix(tmp, "foo-");
assert_eq!(
paths.len(),
1,
"only files starting with 'foo-' must match (not 'foobar-'), got {paths:?}",
);
}
#[test]
fn find_sidecars_by_prefix_empty_when_no_match() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("bar-0001.ktstr.json"), b"{}").unwrap();
let paths = find_sidecars_by_prefix(tmp, "foo-");
assert!(
paths.is_empty(),
"no prefix match must yield empty Vec, got {paths:?}",
);
}
#[test]
fn test_fixture_round_trips_clean() {
let sc = SidecarResult::test_fixture();
let json = serde_json::to_string(&sc).expect("fixture must serialize");
let _loaded: SidecarResult = serde_json::from_str(&json).expect("fixture JSON must parse back");
}
#[test]
fn test_fixture_is_pass_not_skip() {
let sc = SidecarResult::test_fixture();
assert!(sc.passed, "fixture must default to passed=true");
assert!(!sc.skipped, "fixture must default to skipped=false");
}
#[test]
fn test_fixture_host_is_none() {
let sc = SidecarResult::test_fixture();
assert!(sc.host.is_none(), "fixture must default to host=None");
}
#[test]
fn test_fixture_payload_and_metrics_empty() {
let sc = SidecarResult::test_fixture();
assert!(sc.payload.is_none(), "fixture must default to payload=None");
assert!(
sc.metrics.is_empty(),
"fixture must default to metrics=empty"
);
}
#[test]
fn test_fixture_all_collections_empty_by_default() {
let sc = SidecarResult::test_fixture();
assert!(sc.metrics.is_empty(), "metrics must default empty");
assert!(
sc.active_flags.is_empty(),
"active_flags must default empty"
);
assert!(
sc.stimulus_events.is_empty(),
"stimulus_events must default empty"
);
assert!(
sc.verifier_stats.is_empty(),
"verifier_stats must default empty"
);
assert!(sc.sysctls.is_empty(), "sysctls must default empty");
assert!(sc.kargs.is_empty(), "kargs must default empty");
assert!(sc.payload.is_none(), "payload must default None");
assert!(sc.monitor.is_none(), "monitor must default None");
assert!(sc.kvm_stats.is_none(), "kvm_stats must default None");
assert!(
sc.kernel_version.is_none(),
"kernel_version must default None"
);
assert!(
sc.kernel_commit.is_none(),
"kernel_commit must default None"
);
assert!(sc.host.is_none(), "host must default None");
assert!(
sc.timestamp.is_empty(),
"timestamp must default empty String"
);
assert!(sc.run_id.is_empty(), "run_id must default empty String");
assert!(
sc.stats.cgroups.is_empty(),
"stats.cgroups must default empty (ScenarioStats::default)",
);
assert!(sc.passed, "passed must default true");
assert!(!sc.skipped, "skipped must default false");
}
#[test]
fn test_fixture_variant_hash_is_stable() {
let a = sidecar_variant_hash(&SidecarResult::test_fixture());
let b = sidecar_variant_hash(&SidecarResult::test_fixture());
assert_eq!(a, b, "two fresh fixtures must hash identically");
assert_eq!(
a, 0x4ea01e28f65add5e,
"fixture hash drifted — update only if the fixture default \
change is intentional; verify every call site that passes \
the fixture straight into sidecar_variant_hash still expresses \
the intent it had before",
);
}
#[test]
fn sidecar_result_roundtrip() {
let sc = SidecarResult {
test_name: "my_test".to_string(),
topology: "1n2l4c2t".to_string(),
scheduler: "scx_mitosis".to_string(),
scheduler_commit: Some("abc123".to_string()),
project_commit: Some("def4567".to_string()),
payload: None,
metrics: vec![],
passed: true,
skipped: false,
stats: crate::assert::ScenarioStats {
cgroups: vec![CgroupStats {
num_workers: 4,
num_cpus: 2,
avg_off_cpu_pct: 50.0,
min_off_cpu_pct: 40.0,
max_off_cpu_pct: 60.0,
spread: 20.0,
max_gap_ms: 100,
max_gap_cpu: 1,
total_migrations: 5,
..Default::default()
}],
total_workers: 4,
total_cpus: 2,
total_migrations: 5,
worst_spread: 20.0,
worst_gap_ms: 100,
worst_gap_cpu: 1,
..Default::default()
},
monitor: Some(MonitorSummary {
prog_stats_deltas: None,
total_samples: 10,
max_imbalance_ratio: 1.5,
max_local_dsq_depth: 3,
stall_detected: false,
event_deltas: Some(crate::monitor::ScxEventDeltas {
total_fallback: 7,
fallback_rate: 0.5,
max_fallback_burst: 2,
total_dispatch_offline: 0,
total_dispatch_keep_last: 3,
keep_last_rate: 0.2,
total_enq_skip_exiting: 0,
total_enq_skip_migration_disabled: 0,
..Default::default()
}),
schedstat_deltas: None,
..Default::default()
}),
stimulus_events: vec![crate::timeline::StimulusEvent {
elapsed_ms: 500,
label: "StepStart[0]".to_string(),
op_kind: Some("SetCpuset".to_string()),
detail: Some("4 cpus".to_string()),
total_iterations: None,
}],
work_type: "SpinWait".to_string(),
active_flags: Vec::new(),
verifier_stats: vec![],
kvm_stats: None,
sysctls: vec![],
kargs: vec![],
kernel_version: None,
kernel_commit: Some("kabcde7".to_string()),
timestamp: String::new(),
run_id: String::new(),
host: None,
cleanup_duration_ms: Some(123),
run_source: Some(SIDECAR_RUN_SOURCE_LOCAL.to_string()),
};
let json = serde_json::to_string_pretty(&sc).unwrap();
let loaded: SidecarResult = serde_json::from_str(&json).unwrap();
let SidecarResult {
test_name,
topology,
scheduler,
scheduler_commit,
project_commit,
payload,
metrics,
passed,
skipped,
stats,
monitor,
stimulus_events,
work_type,
active_flags,
verifier_stats,
kvm_stats,
sysctls,
kargs,
kernel_version,
kernel_commit,
timestamp,
run_id,
host,
cleanup_duration_ms,
run_source,
} = loaded;
assert_eq!(test_name, "my_test");
assert_eq!(topology, "1n2l4c2t");
assert_eq!(scheduler, "scx_mitosis");
assert_eq!(work_type, "SpinWait");
assert_eq!(scheduler_commit.as_deref(), Some("abc123"));
assert_eq!(project_commit.as_deref(), Some("def4567"));
assert_eq!(
kernel_commit.as_deref(),
Some("kabcde7"),
"kernel_commit must round-trip the literal string \
populated on the write side, including the 7-char \
hex shape `detect_kernel_commit` produces. The \
fixture uses `kabcde7` (hex-only) to make accidental \
field-swap regressions with project_commit / \
scheduler_commit obvious — each commit field carries \
a distinct token.",
);
assert_eq!(payload, None, "fixture declared no payload");
assert_eq!(kvm_stats, None, "fixture declared no kvm_stats");
assert_eq!(kernel_version, None, "fixture declared no kernel_version");
assert_eq!(host, None, "fixture declared no host context");
assert_eq!(timestamp, "", "fixture used empty-string timestamp");
assert_eq!(run_id, "", "fixture used empty-string run_id");
assert!(passed);
assert!(!skipped, "fixture declared skipped=false");
assert!(metrics.is_empty(), "fixture declared empty metrics");
assert!(
active_flags.is_empty(),
"fixture declared empty active_flags",
);
assert!(
verifier_stats.is_empty(),
"fixture declared empty verifier_stats",
);
assert!(sysctls.is_empty(), "fixture declared empty sysctls");
assert!(kargs.is_empty(), "fixture declared empty kargs");
assert_eq!(stats.total_workers, 4);
assert_eq!(stats.cgroups.len(), 1);
assert_eq!(stats.cgroups[0].num_workers, 4);
assert_eq!(stats.worst_spread, 20.0);
let mon = monitor.unwrap();
assert_eq!(mon.total_samples, 10);
assert_eq!(mon.max_imbalance_ratio, 1.5);
assert_eq!(mon.max_local_dsq_depth, 3);
assert!(!mon.stall_detected);
let deltas = mon.event_deltas.unwrap();
assert_eq!(deltas.total_fallback, 7);
assert_eq!(deltas.total_dispatch_keep_last, 3);
assert_eq!(stimulus_events.len(), 1);
assert_eq!(stimulus_events[0].label, "StepStart[0]");
assert_eq!(
cleanup_duration_ms,
Some(123),
"cleanup_duration_ms round-tripped",
);
assert_eq!(
run_source.as_deref(),
Some(SIDECAR_RUN_SOURCE_LOCAL),
"run_source must round-trip the literal `local` populated on \
the write side, including the absent-vs-populated distinction",
);
}
#[test]
fn sidecar_result_roundtrip_all_fields_round_trip() {
use crate::assert::{CgroupStats, ScenarioStats};
use crate::host_context::HostContext;
use crate::monitor::MonitorSummary;
use crate::monitor::bpf_prog::ProgVerifierStats;
use crate::test_support::{Metric, MetricSource, MetricStream, PayloadMetrics, Polarity};
use crate::timeline::StimulusEvent;
let sc = SidecarResult {
test_name: "audit".to_string(),
topology: "8n8l16c2t".to_string(),
scheduler: "scx_audit".to_string(),
scheduler_commit: Some("deadbeef1234567890abcdef".to_string()),
project_commit: Some("cafebab-dirty".to_string()),
payload: Some("audit_payload".to_string()),
metrics: vec![PayloadMetrics {
payload_index: 0,
metrics: vec![Metric {
name: "audit_metric".to_string(),
value: 42.0,
polarity: Polarity::HigherBetter,
unit: "audits".to_string(),
source: MetricSource::Json,
stream: MetricStream::Stdout,
}],
exit_code: 7,
}],
passed: false,
skipped: true,
stats: ScenarioStats {
cgroups: vec![CgroupStats {
num_workers: 3,
..Default::default()
}],
total_workers: 3,
..Default::default()
},
monitor: Some(MonitorSummary {
total_samples: 17,
..Default::default()
}),
stimulus_events: vec![StimulusEvent {
elapsed_ms: 123,
label: "audit_event".to_string(),
op_kind: None,
detail: None,
total_iterations: None,
}],
work_type: "AuditWork".to_string(),
active_flags: vec!["flag_a".to_string(), "flag_b".to_string()],
verifier_stats: vec![ProgVerifierStats {
name: "audit_prog".to_string(),
verified_insns: 999,
}],
kvm_stats: Some(crate::vmm::KvmStatsTotals::default()),
sysctls: vec!["sysctl.kernel.audit_sysctl=1".to_string()],
kargs: vec!["audit_karg".to_string()],
kernel_version: Some("6.99.0".to_string()),
kernel_commit: Some("kabcde7-dirty".to_string()),
timestamp: "audit-timestamp".to_string(),
run_id: "audit-run-id".to_string(),
host: Some(HostContext {
kernel_name: Some("AuditLinux".to_string()),
..Default::default()
}),
cleanup_duration_ms: Some(987),
run_source: Some(SIDECAR_RUN_SOURCE_CI.to_string()),
};
let json = serde_json::to_string(&sc).expect("serialize");
let loaded: SidecarResult = serde_json::from_str(&json).expect("deserialize");
assert_eq!(loaded.test_name, "audit");
assert_eq!(loaded.topology, "8n8l16c2t");
assert_eq!(loaded.scheduler, "scx_audit");
assert_eq!(
loaded.scheduler_commit.as_deref(),
Some("deadbeef1234567890abcdef"),
"scheduler_commit must round-trip the literal string \
populated on the write side — not collapse to None via \
a missing serde attribute or default fallback",
);
assert_eq!(
loaded.project_commit.as_deref(),
Some("cafebab-dirty"),
"project_commit must round-trip the literal string \
populated on the write side, including the `-dirty` \
suffix that `detect_project_commit` appends — a \
regression that stripped the suffix or substituted \
None for a populated value would surface here. \
Fixture uses 7-char hex (`cafebab`) to match the \
`oid::to_hex_with_len(7)` shape `detect_project_commit` \
produces in production.",
);
assert_eq!(loaded.payload.as_deref(), Some("audit_payload"));
assert_eq!(loaded.metrics.len(), 1);
assert_eq!(loaded.metrics[0].exit_code, 7);
assert_eq!(loaded.metrics[0].metrics.len(), 1);
assert_eq!(loaded.metrics[0].metrics[0].name, "audit_metric");
assert_eq!(loaded.metrics[0].metrics[0].value, 42.0);
assert!(!loaded.passed, "passed must survive as false");
assert!(loaded.skipped, "skipped must survive as true");
assert_eq!(loaded.stats.total_workers, 3);
assert_eq!(loaded.stats.cgroups.len(), 1);
assert_eq!(loaded.stats.cgroups[0].num_workers, 3);
let mon = loaded.monitor.expect("monitor round-trips");
assert_eq!(mon.total_samples, 17);
assert_eq!(loaded.stimulus_events.len(), 1);
assert_eq!(loaded.stimulus_events[0].label, "audit_event");
assert_eq!(loaded.stimulus_events[0].elapsed_ms, 123);
assert_eq!(loaded.work_type, "AuditWork");
assert_eq!(loaded.active_flags, vec!["flag_a", "flag_b"]);
assert_eq!(loaded.verifier_stats.len(), 1);
assert_eq!(loaded.verifier_stats[0].name, "audit_prog");
assert_eq!(loaded.verifier_stats[0].verified_insns, 999);
assert!(
loaded.kvm_stats.is_some(),
"kvm_stats must round-trip as Some"
);
assert_eq!(loaded.sysctls, vec!["sysctl.kernel.audit_sysctl=1"]);
assert_eq!(loaded.kargs, vec!["audit_karg"]);
assert_eq!(loaded.kernel_version.as_deref(), Some("6.99.0"));
assert_eq!(
loaded.kernel_commit.as_deref(),
Some("kabcde7-dirty"),
"kernel_commit must round-trip the literal string \
populated on the write side, including the `-dirty` \
suffix that `detect_kernel_commit` appends. Fixture \
uses 7-char hex (`kabcde7`) to match the \
`oid::to_hex_with_len(7)` shape `detect_kernel_commit` \
produces in production. The leading `k` in the fixture \
token makes a project_commit / kernel_commit field-swap \
regression visible — each commit field carries a \
distinct token in the audit fixture.",
);
assert_eq!(loaded.timestamp, "audit-timestamp");
assert_eq!(loaded.run_id, "audit-run-id");
let host = loaded.host.expect("host round-trips");
assert_eq!(host.kernel_name.as_deref(), Some("AuditLinux"));
assert_eq!(loaded.cleanup_duration_ms, Some(987));
assert_eq!(
loaded.run_source.as_deref(),
Some(SIDECAR_RUN_SOURCE_CI),
"run_source must round-trip the literal `ci` populated on \
the write side. Audit fixture uses `ci` (vs `local` in \
the sibling roundtrip) so a write-vs-read field-swap \
regression that mapped one tag onto another would \
surface in this audit pass even if the sibling test \
did not detect it.",
);
}
#[test]
fn sidecar_result_roundtrip_no_monitor() {
let sc = SidecarResult {
test_name: "eevdf_test".to_string(),
topology: "1n1l2c1t".to_string(),
passed: false,
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sc).unwrap();
let loaded: SidecarResult = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.test_name, "eevdf_test");
assert!(!loaded.passed);
assert!(loaded.monitor.is_none());
assert!(loaded.stimulus_events.is_empty());
assert!(
json.contains("\"monitor\":null"),
"monitor=None must serialize as `\"monitor\":null`, not be omitted: {json}",
);
}
#[test]
fn sidecar_result_missing_required_field_rejected_by_deserialize() {
const REQUIRED_NON_OPTION_FIELDS: &[&str] = &[
"test_name",
"topology",
"scheduler",
"metrics",
"passed",
"skipped",
"stats",
"stimulus_events",
"work_type",
"active_flags",
"verifier_stats",
"sysctls",
"kargs",
"timestamp",
"run_id",
];
let fixture = SidecarResult::test_fixture();
let full = match serde_json::to_value(&fixture).unwrap() {
serde_json::Value::Object(m) => m,
other => panic!("expected object, got {other:?}"),
};
for field in REQUIRED_NON_OPTION_FIELDS {
let mut obj = full.clone();
assert!(
obj.remove(*field).is_some(),
"SidecarResult test fixture must emit `{field}` for its \
rejection case to be meaningful — the required-fields \
list has drifted from the struct definition",
);
let json = serde_json::Value::Object(obj).to_string();
let err = serde_json::from_str::<SidecarResult>(&json)
.err()
.unwrap_or_else(|| {
panic!(
"deserialize must reject SidecarResult with `{field}` removed, \
but succeeded — a regression may have added \
`#[serde(default)]` to this field",
)
});
let msg = format!("{err}");
assert!(
msg.contains(field),
"missing-field error for `{field}` must name the field; got: {msg}",
);
}
}
#[test]
fn sidecar_result_rename_contract_old_source_key_lands_run_source_none() {
let fixture = SidecarResult::test_fixture();
let full = match serde_json::to_value(&fixture).unwrap() {
serde_json::Value::Object(m) => m,
other => panic!("expected object, got {other:?}"),
};
let mut obj_old = full.clone();
obj_old.remove("run_source");
obj_old.insert(
"source".to_string(),
serde_json::Value::String("ci".to_string()),
);
let json_old = serde_json::Value::Object(obj_old).to_string();
let parsed_old: SidecarResult = serde_json::from_str(&json_old).expect(
"old-key sidecar must still deserialize — \
SidecarResult does not set deny_unknown_fields, \
so the unrecognised `\"source\"` key is silently dropped",
);
assert_eq!(
parsed_old.run_source, None,
"old `\"source\": \"ci\"` key must land run_source = None \
per the documented data-loss contract; a regression that \
added `#[serde(alias = \"source\")]` would yield Some(\"ci\") here",
);
let mut obj_new = full.clone();
obj_new.insert(
"run_source".to_string(),
serde_json::Value::String("ci".to_string()),
);
let json_new = serde_json::Value::Object(obj_new).to_string();
let parsed_new: SidecarResult =
serde_json::from_str(&json_new).expect("new-key sidecar must deserialize cleanly");
assert_eq!(
parsed_new.run_source.as_deref(),
Some("ci"),
"new `\"run_source\": \"ci\"` key must populate \
run_source — a regression breaking the new-key path \
would yield None here",
);
let mut obj_both = full.clone();
obj_both.insert(
"run_source".to_string(),
serde_json::Value::String("ci".to_string()),
);
obj_both.insert(
"source".to_string(),
serde_json::Value::String("local".to_string()),
);
let json_both = serde_json::Value::Object(obj_both).to_string();
let parsed_both: SidecarResult =
serde_json::from_str(&json_both).expect("both-keys sidecar must deserialize cleanly");
assert_eq!(
parsed_both.run_source.as_deref(),
Some("ci"),
"with both keys present, new `\"run_source\"` must win \
— the old `\"source\"` is silently dropped, NOT used \
as a fallback. A regression that processed `\"source\"` \
as an alias would surface here as Some(\"local\")",
);
}
#[test]
fn collect_sidecars_empty_dir() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let results = collect_sidecars(tmp_dir.path());
assert!(results.is_empty());
}
#[test]
fn collect_sidecars_nonexistent_dir() {
let results = collect_sidecars(std::path::Path::new("/nonexistent/path"));
assert!(results.is_empty());
}
#[test]
fn collect_sidecars_reads_json() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let sc = SidecarResult {
test_name: "test_x".to_string(),
topology: "1n1l2c1t".to_string(),
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sc).unwrap();
std::fs::write(tmp.join("test_x.ktstr.json"), &json).unwrap();
std::fs::write(tmp.join("other.json"), r#"{"key":"val"}"#).unwrap();
let results = collect_sidecars(tmp);
assert_eq!(results.len(), 1);
assert_eq!(results[0].test_name, "test_x");
}
#[test]
fn collect_sidecars_recurses_one_level() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let sub = tmp.join("job-0");
std::fs::create_dir_all(&sub).unwrap();
let sc = SidecarResult {
test_name: "nested_test".to_string(),
topology: "1n2l4c2t".to_string(),
scheduler: "scx_mitosis".to_string(),
passed: false,
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sc).unwrap();
std::fs::write(sub.join("nested_test.ktstr.json"), &json).unwrap();
let results = collect_sidecars(tmp);
assert_eq!(results.len(), 1);
assert_eq!(results[0].test_name, "nested_test");
assert!(!results[0].passed);
}
#[test]
fn collect_sidecars_does_not_recurse_past_one_level() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let top_sub = tmp.join("job-0");
let deep_sub = top_sub.join("replay-0");
std::fs::create_dir_all(&deep_sub).unwrap();
let sc = |name: &str| SidecarResult {
test_name: name.to_string(),
..SidecarResult::test_fixture()
};
std::fs::write(
top_sub.join("top_level.ktstr.json"),
serde_json::to_string(&sc("top_level")).unwrap(),
)
.unwrap();
std::fs::write(
deep_sub.join("deep_level.ktstr.json"),
serde_json::to_string(&sc("deep_level")).unwrap(),
)
.unwrap();
let results = collect_sidecars(tmp);
let names: Vec<&str> = results.iter().map(|r| r.test_name.as_str()).collect();
assert_eq!(
names,
vec!["top_level"],
"collect_sidecars must see only the one-level-deep sidecar, not the two-level one"
);
}
#[test]
fn collect_sidecars_skips_invalid_json() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("bad.ktstr.json"), "not json").unwrap();
let results = collect_sidecars(tmp);
assert!(results.is_empty());
}
#[test]
fn collect_sidecars_skips_non_ktstr_json() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("other.json"), r#"{"test":"val"}"#).unwrap();
let results = collect_sidecars(tmp);
assert!(results.is_empty());
}
#[test]
fn sidecar_result_work_type_field() {
let sc = SidecarResult {
work_type: "Bursty".to_string(),
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sc).unwrap();
let loaded: SidecarResult = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.work_type, "Bursty");
}
#[test]
fn write_sidecar_defaults_to_target_dir_without_env() {
let _lock = lock_env();
let target_dir = tempfile::TempDir::new().unwrap();
let _env_target = EnvVarGuard::set("CARGO_TARGET_DIR", target_dir.path());
let _env_sidecar = EnvVarGuard::remove("KTSTR_SIDECAR_DIR");
let _env_kernel = EnvVarGuard::remove("KTSTR_KERNEL");
let dir = sidecar_dir();
let kernel = detect_kernel_version();
let commit = detect_project_commit();
let expected = runs_root().join(format_run_dirname(kernel.as_deref(), commit.as_deref()));
assert_eq!(dir, expected);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__sidecar_default_dir__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let check_result = AssertResult::pass();
write_sidecar(&entry, &vm_result, &[], &check_result, "SpinWait", &[], &[]).unwrap();
let paths = find_sidecars_by_prefix(&dir, "__sidecar_default_dir__-");
assert_eq!(
paths.len(),
1,
"single `write_sidecar` call against prefix \
`__sidecar_default_dir__-` must produce exactly one \
file; got {} ({paths:?}). If >1, either the variant \
hash collided for this test's variant-field tuple or \
`pre_clear_run_dir_once`'s per-directory keying failed \
to wipe a stale sidecar from a prior crashed run.",
paths.len(),
);
}
#[test]
fn sidecar_dir_empty_override_falls_back_to_default() {
let _lock = lock_env();
let target_dir = tempfile::TempDir::new().unwrap();
let _env_target = EnvVarGuard::set("CARGO_TARGET_DIR", target_dir.path());
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", "");
let _env_kernel = EnvVarGuard::remove("KTSTR_KERNEL");
let dir = sidecar_dir();
let kernel = detect_kernel_version();
let commit = detect_project_commit();
let expected = runs_root().join(format_run_dirname(kernel.as_deref(), commit.as_deref()));
assert_eq!(
dir, expected,
"empty KTSTR_SIDECAR_DIR must fall back to the default \
`runs_root().join(format_run_dirname(...))` path, NOT \
return PathBuf::from(\"\"). A regression that dropped \
the `is_empty()` filter on the override read would \
surface here as `dir == PathBuf::from(\"\")`.",
);
assert_ne!(
dir,
std::path::PathBuf::new(),
"sidecar_dir must never return an empty path",
);
}
#[test]
fn format_run_dirname_clean_commit() {
assert_eq!(
format_run_dirname(Some("6.14.2"), Some("abc1234")),
"6.14.2-abc1234",
"clean dirname must be `{{kernel}}-{{project_commit}}`",
);
}
#[test]
fn format_run_dirname_dirty_commit() {
assert_eq!(
format_run_dirname(Some("6.14.2"), Some("abc1234-dirty")),
"6.14.2-abc1234-dirty",
"dirty dirname must pass the `-dirty` suffix through verbatim",
);
}
#[test]
fn format_run_dirname_unknown_commit() {
assert_eq!(
format_run_dirname(Some("6.14.2"), None),
"6.14.2-unknown",
"missing commit must collapse to `{{kernel}}-unknown` sentinel",
);
}
#[test]
fn format_run_dirname_unknown_kernel() {
assert_eq!(
format_run_dirname(None, Some("abc1234")),
"unknown-abc1234",
"missing kernel must collapse to `unknown-{{project_commit}}` sentinel",
);
}
#[test]
fn format_run_dirname_both_unknown_collide() {
assert_eq!(
format_run_dirname(None, None),
"unknown-unknown",
"both-missing case must produce `unknown-unknown` — the documented \
collision the operator must disambiguate via KTSTR_SIDECAR_DIR or git",
);
}
#[test]
fn pre_clear_run_dir_once_wipes_existing_sidecars() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("test_a-0000.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("test_b-1111.ktstr.json"), b"{}").unwrap();
assert_eq!(
std::fs::read_dir(tmp).unwrap().count(),
2,
"fixture precondition: tempdir must contain two sidecars",
);
pre_clear_run_dir_once(tmp);
let remaining: Vec<_> = std::fs::read_dir(tmp)
.unwrap()
.flatten()
.map(|e| e.file_name())
.collect();
assert!(
remaining.is_empty(),
"every *.ktstr.json file must be wiped; got {remaining:?}",
);
}
#[test]
fn pre_clear_run_dir_once_skips_subdirs_and_non_sidecars() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(tmp.join("victim-0000.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("README.md"), b"keep").unwrap();
std::fs::write(tmp.join("other.json"), b"{}").unwrap();
std::fs::write(tmp.join("partial.ktstr.json.tmp"), b"{}").unwrap();
let sub = tmp.join("job-1");
std::fs::create_dir(&sub).unwrap();
std::fs::write(sub.join("nested-0000.ktstr.json"), b"{}").unwrap();
pre_clear_run_dir_once(tmp);
assert!(
!tmp.join("victim-0000.ktstr.json").exists(),
"top-level *.ktstr.json file must be wiped",
);
assert!(
tmp.join("README.md").exists(),
"non-sidecar file must survive",
);
assert!(
tmp.join("other.json").exists(),
"bare *.json (no .ktstr. infix) must survive",
);
assert!(
tmp.join("partial.ktstr.json.tmp").exists(),
"non-`.json` extension must survive even with .ktstr. infix",
);
assert!(sub.exists(), "subdirectory must survive");
assert!(
sub.join("nested-0000.ktstr.json").exists(),
"sidecar inside subdirectory must survive (pre-clear is shallow)",
);
}
#[test]
fn pre_clear_run_dir_once_silent_on_missing_dir() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let nonexistent = tmp_dir.path().join("does_not_exist_yet");
assert!(
!nonexistent.exists(),
"fixture precondition: dir must not exist"
);
pre_clear_run_dir_once(&nonexistent);
assert!(
!nonexistent.exists(),
"pre_clear must not create the dir as a side effect",
);
}
#[test]
fn pre_clear_run_dir_once_wipes_staging_files() {
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
std::fs::write(
tmp.join("test_a-0000000000000000.ktstr.json.tmp.12345.0001"),
b"{}",
)
.unwrap();
std::fs::write(
tmp.join("test_b-1111111111111111.ktstr.json.tmp.67890.0002"),
b"{}",
)
.unwrap();
std::fs::write(tmp.join("test_c-2222222222222222.ktstr.json"), b"{}").unwrap();
std::fs::write(tmp.join("README.md"), b"keep").unwrap();
assert_eq!(
std::fs::read_dir(tmp).unwrap().count(),
4,
"fixture precondition: tempdir must contain 2 staging \
files, 1 sidecar, and 1 unrelated file",
);
pre_clear_run_dir_once(tmp);
assert!(
!tmp.join("test_a-0000000000000000.ktstr.json.tmp.12345.0001")
.exists(),
"orphaned staging file (writer crash before rename) must be reaped",
);
assert!(
!tmp.join("test_b-1111111111111111.ktstr.json.tmp.67890.0002")
.exists(),
"second orphaned staging file must be reaped",
);
assert!(
!tmp.join("test_c-2222222222222222.ktstr.json").exists(),
"live sidecar from prior run must also be wiped in the same sweep",
);
assert!(
tmp.join("README.md").exists(),
"unrelated file must survive — sweep is shape-scoped, \
not a wholesale directory wipe",
);
}
#[test]
fn pre_clear_run_dir_once_keys_per_directory() {
let tmp_a = tempfile::TempDir::new().unwrap();
let tmp_b = tempfile::TempDir::new().unwrap();
std::fs::write(tmp_a.path().join("a-0000.ktstr.json"), b"{}").unwrap();
pre_clear_run_dir_once(tmp_a.path());
assert!(
!tmp_a.path().join("a-0000.ktstr.json").exists(),
"first call against A must wipe A's sidecar",
);
std::fs::write(tmp_a.path().join("a-1111.ktstr.json"), b"{}").unwrap();
pre_clear_run_dir_once(tmp_a.path());
assert!(
tmp_a.path().join("a-1111.ktstr.json").exists(),
"second call against A must be a no-op (cache hit) — \
the post-prime sidecar must survive. A regression to \
OnceLock<()> or a HashSet that ignores the key would \
leak this assertion.",
);
std::fs::write(tmp_b.path().join("b-0000.ktstr.json"), b"{}").unwrap();
pre_clear_run_dir_once(tmp_b.path());
assert!(
!tmp_b.path().join("b-0000.ktstr.json").exists(),
"first call against B must wipe B's sidecar — proves the \
per-dir keying distinguishes A from B (a OnceLock<()> \
that fired once for A would leak this assertion).",
);
}
#[test]
fn warn_unknown_project_commit_inner_emits_on_first_call() {
let gate = std::sync::OnceLock::new();
let mut sink: Vec<u8> = Vec::new();
warn_unknown_project_commit_inner(&gate, &mut sink);
assert!(
!sink.is_empty(),
"first call must emit bytes to the sink; got empty",
);
}
#[test]
fn warn_unknown_project_commit_inner_emits_expected_substring() {
let gate = std::sync::OnceLock::new();
let mut sink: Vec<u8> = Vec::new();
warn_unknown_project_commit_inner(&gate, &mut sink);
let captured = String::from_utf8(sink).expect("warning text must be UTF-8");
assert!(
captured.contains("WARNING:"),
"warning must carry the WARNING severity tag; got: {captured:?}",
);
assert!(
captured.contains("KTSTR_SIDECAR_DIR"),
"warning must reference KTSTR_SIDECAR_DIR as the remediation \
knob — operators rely on this hint to disambiguate \
non-git runs; got: {captured:?}",
);
}
#[test]
fn warn_unknown_project_commit_inner_second_call_is_no_op() {
let gate = std::sync::OnceLock::new();
let mut sink: Vec<u8> = Vec::new();
warn_unknown_project_commit_inner(&gate, &mut sink);
let after_first = sink.len();
assert!(
after_first > 0,
"fixture precondition: first call must emit bytes",
);
warn_unknown_project_commit_inner(&gate, &mut sink);
assert_eq!(
sink.len(),
after_first,
"second call against the same gate must NOT append bytes — \
the OnceLock<()> gating is the load-bearing invariant; got \
len {} (expected {after_first})",
sink.len(),
);
}
#[test]
fn newest_run_dir_skips_dotfile_subdirectories() {
use std::thread::sleep;
use std::time::Duration;
let _lock = lock_env();
let target_dir = tempfile::TempDir::new().unwrap();
let _env_target = EnvVarGuard::set("CARGO_TARGET_DIR", target_dir.path());
let runs = target_dir.path().join("ktstr");
std::fs::create_dir(&runs).expect("mkdir runs root");
let real = runs.join("real-run");
std::fs::create_dir(&real).expect("mkdir real run dir");
sleep(Duration::from_millis(50));
std::fs::create_dir(runs.join(".locks")).expect("mkdir .locks");
let got = newest_run_dir().expect("non-empty runs root must yield Some");
assert_eq!(
got, real,
"newest_run_dir must pick the real run dir even when \
.locks/ has a newer mtime — a regression that drops \
the dotfile filter would surface here as `.locks/` \
winning the mtime contest",
);
}
#[test]
fn newest_run_dir_yields_none_when_only_dotfiles_exist() {
let _lock = lock_env();
let target_dir = tempfile::TempDir::new().unwrap();
let _env_target = EnvVarGuard::set("CARGO_TARGET_DIR", target_dir.path());
let runs = target_dir.path().join("ktstr");
std::fs::create_dir(&runs).expect("mkdir runs root");
std::fs::create_dir(runs.join(".locks")).expect("mkdir .locks");
std::fs::create_dir(runs.join(".cache")).expect("mkdir .cache");
let got = newest_run_dir();
assert!(
got.is_none(),
"runs root with only dotfile subdirs must yield None; got {got:?}",
);
}
#[test]
fn is_run_directory_accepts_non_dotfile_subdir() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::create_dir(tmp.path().join("real-run")).unwrap();
let entry = std::fs::read_dir(tmp.path())
.unwrap()
.next()
.unwrap()
.unwrap();
assert!(
super::is_run_directory(&entry),
"non-dotfile subdir must be accepted",
);
}
#[test]
fn is_run_directory_rejects_dotfile_subdir() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::create_dir(tmp.path().join(".locks")).unwrap();
let entry = std::fs::read_dir(tmp.path())
.unwrap()
.next()
.unwrap()
.unwrap();
assert!(
!super::is_run_directory(&entry),
"dotfile subdir must be rejected",
);
}
#[test]
fn is_run_directory_rejects_regular_files() {
let tmp = tempfile::TempDir::new().unwrap();
std::fs::write(tmp.path().join("regular-file"), b"x").unwrap();
let entry = std::fs::read_dir(tmp.path())
.unwrap()
.next()
.unwrap()
.unwrap();
assert!(
!super::is_run_directory(&entry),
"regular file must be rejected",
);
}
#[test]
fn run_dir_lock_path_returns_expected_shape() {
let dir = std::path::Path::new("/runs-root/6.14.2-deadbee");
let lock = super::run_dir_lock_path(dir).expect("non-root dir must yield Some");
assert_eq!(
lock,
std::path::PathBuf::from("/runs-root/.locks/6.14.2-deadbee.lock"),
);
}
#[test]
fn run_dir_lock_path_no_parent_returns_none() {
let lock = super::run_dir_lock_path(std::path::Path::new("/"));
assert!(
lock.is_none(),
"root path must yield None (no parent), got {lock:?}",
);
}
#[test]
fn acquire_run_dir_flock_creates_locks_subdir_lazily() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("6.14.2-deadbee");
std::fs::create_dir_all(&dir).unwrap();
let fd = super::acquire_run_dir_flock_with_timeout(&dir, std::time::Duration::from_secs(1))
.expect("first acquire must succeed against an uncontended dir");
assert!(
tmp.path().join(".locks").exists(),
".locks/ subdirectory must be created lazily on first acquire",
);
assert!(
tmp.path().join(".locks/6.14.2-deadbee.lock").exists(),
"lockfile must exist on disk after acquire",
);
drop(fd);
assert!(
tmp.path().join(".locks/6.14.2-deadbee.lock").exists(),
"lockfile sentinel must persist after fd drop — \
try_flock's contract is fd-bound release, not file unlink",
);
}
#[test]
fn acquire_run_dir_flock_releases_on_drop() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("key");
std::fs::create_dir_all(&dir).unwrap();
let fd1 = super::acquire_run_dir_flock_with_timeout(&dir, std::time::Duration::from_secs(1))
.expect("first acquire");
drop(fd1);
let fd2 = super::acquire_run_dir_flock_with_timeout(&dir, std::time::Duration::from_secs(1))
.expect(
"second acquire after drop must succeed — a regression that \
fails to release the kernel flock on OwnedFd::drop would \
leak this assertion",
);
drop(fd2);
}
#[test]
fn acquire_run_dir_flock_times_out_when_peer_holds_lock() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("contended-key");
std::fs::create_dir_all(&dir).unwrap();
let lock_path = super::run_dir_lock_path(&dir).unwrap();
std::fs::create_dir_all(lock_path.parent().unwrap()).unwrap();
let _peer_fd = crate::flock::try_flock(&lock_path, crate::flock::FlockMode::Exclusive)
.expect("peer flock attempt")
.expect("peer must acquire on a fresh lockfile");
let start = std::time::Instant::now();
let err =
super::acquire_run_dir_flock_with_timeout(&dir, std::time::Duration::from_millis(300))
.expect_err("acquire must fail while peer holds LOCK_EX");
let elapsed = start.elapsed();
assert!(
elapsed >= std::time::Duration::from_millis(250),
"acquire must wait ~timeout before erroring; elapsed={elapsed:?}",
);
let msg = format!("{err:#}");
assert!(
msg.contains("timed out"),
"error must surface the timeout cause; got: {msg}",
);
assert!(
msg.contains("LOCK_EX"),
"error must name the flock mode for operator triage; got: {msg}",
);
}
#[test]
fn write_sidecar_same_dir_is_last_writer_wins_after_pre_clear() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry_a = KtstrTestEntry {
name: "__reuse_first_run__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
write_sidecar(&entry_a, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
assert_eq!(
find_sidecars_by_prefix(tmp, "__reuse_first_run__-").len(),
1,
"first invocation must write its sidecar",
);
pre_clear_run_dir_once(tmp);
assert_eq!(
find_sidecars_by_prefix(tmp, "__reuse_first_run__-").len(),
0,
"pre-clear must wipe the first invocation's sidecar before \
the second invocation writes — this is the last-writer-wins \
contract",
);
let entry_b = KtstrTestEntry {
name: "__reuse_second_run__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
write_sidecar(&entry_b, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
assert_eq!(
find_sidecars_by_prefix(tmp, "__reuse_first_run__-").len(),
0,
"first invocation's sidecar must remain wiped after second invocation writes",
);
assert_eq!(
find_sidecars_by_prefix(tmp, "__reuse_second_run__-").len(),
1,
"second invocation's sidecar must be the only sidecar in the dir",
);
}
#[test]
fn write_sidecar_override_does_not_pre_clear() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
std::fs::write(tmp.join("__preserved__-0000.ktstr.json"), b"{}").unwrap();
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__override_skips_preclear__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
assert!(
tmp.join("__preserved__-0000.ktstr.json").exists(),
"pre-existing sidecar in override dir must NOT be pre-cleared — \
operator-chosen directories are owned by the operator and \
must not lose data on `write_sidecar`",
);
assert_eq!(
find_sidecars_by_prefix(tmp, "__override_skips_preclear__-").len(),
1,
"new sidecar must be written alongside the preserved one",
);
}
#[test]
fn write_sidecar_default_path_two_writes_both_survive() {
let _lock = lock_env();
let target_dir = tempfile::TempDir::new().unwrap();
let _env_target = EnvVarGuard::set("CARGO_TARGET_DIR", target_dir.path());
let _env_sidecar = EnvVarGuard::remove("KTSTR_SIDECAR_DIR");
let _env_kernel = EnvVarGuard::remove("KTSTR_KERNEL");
let dir = sidecar_dir();
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry_first = KtstrTestEntry {
name: "__b3_first__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let entry_second = KtstrTestEntry {
name: "__b3_second__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
write_sidecar(&entry_first, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
assert_eq!(
find_sidecars_by_prefix(&dir, "__b3_first__-").len(),
1,
"first write must produce its sidecar",
);
write_sidecar(&entry_second, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
let first_count = find_sidecars_by_prefix(&dir, "__b3_first__-").len();
let second_count = find_sidecars_by_prefix(&dir, "__b3_second__-").len();
assert_eq!(
first_count, 1,
"first sidecar must survive the second write — a count of 0 \
reveals the canonicalize-cache-split regression: pre-clear \
ran a second time and wiped sidecar 1. Move `create_dir_all` \
before `pre_clear_run_dir_once` so canonicalize sees the \
same dir on both calls.",
);
assert_eq!(second_count, 1, "second sidecar must land normally",);
}
#[test]
fn write_sidecar_writes_file() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__sidecar_write_test__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let check_result = AssertResult::pass();
write_sidecar(&entry, &vm_result, &[], &check_result, "SpinWait", &[], &[]).unwrap();
let path = find_single_sidecar_by_prefix(tmp, "__sidecar_write_test__-");
let data = std::fs::read_to_string(&path).unwrap();
let loaded: SidecarResult = serde_json::from_str(&data).unwrap();
assert_eq!(loaded.test_name, "__sidecar_write_test__");
assert!(loaded.passed);
assert!(!loaded.skipped, "pass result is not a skip");
let host = loaded
.host
.as_ref()
.expect("write_sidecar must populate host field from collect_host_context");
assert_eq!(host.kernel_name.as_deref(), Some("Linux"));
assert!(
host.kernel_cmdline.is_some(),
"write_sidecar must capture full HostContext, not Default::default() — \
/proc/cmdline is always readable on Linux (see host_context tests)",
);
assert!(
host.kernel_release.is_some(),
"write_sidecar must capture kernel_release — uname() is \
filesystem-independent; a None here means the default \
substitution bypassed the full collect_host_context()",
);
}
#[test]
fn write_sidecar_variant_hash_distinguishes_active_flags() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__flagvariant_test__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
let flags_a = vec!["llc".to_string()];
let flags_b = vec!["llc".to_string(), "steal".to_string()];
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &flags_a, &[]).unwrap();
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &flags_b, &[]).unwrap();
let paths = find_sidecars_by_prefix(tmp, "__flagvariant_test__-");
assert_eq!(
paths.len(),
2,
"two active_flags variants must produce two distinct files, got {paths:?}"
);
}
#[test]
fn write_sidecar_variant_hash_is_order_invariant_for_active_flags() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__flagorder_test__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
let forward = vec!["llc".to_string(), "steal".to_string()];
let reversed = vec!["steal".to_string(), "llc".to_string()];
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &forward, &[]).unwrap();
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &reversed, &[]).unwrap();
let paths = find_sidecars_by_prefix(tmp, "__flagorder_test__-");
assert_eq!(
paths.len(),
1,
"reversed-order writes of the same flag SET must \
collapse to a single canonical sidecar filename \
(overwrite); got {paths:?}. If this fails with \
`paths.len() == 2`, the write path has regressed to \
hashing caller-order flags — re-sort via \
`canonicalize_active_flags` in both write_sidecar \
and write_skip_sidecar.",
);
let path = &paths[0];
let data = std::fs::read_to_string(path).expect("read canonical sidecar");
let loaded: SidecarResult = serde_json::from_str(&data).expect("deserialize canonical sidecar");
assert_eq!(
loaded.active_flags,
vec!["llc".to_string(), "steal".to_string()],
"on-disk active_flags must be sorted in \
`scenario::flags::ALL` positional order; got: {:?}",
loaded.active_flags,
);
}
#[test]
fn sidecar_variant_hash_is_order_invariant_for_sysctls_and_kargs() {
let forward = SidecarResult {
sysctls: vec![
"sysctl.a=1".to_string(),
"sysctl.b=2".to_string(),
"sysctl.c=3".to_string(),
],
kargs: vec![
"karg_alpha".to_string(),
"karg_beta".to_string(),
"karg_gamma".to_string(),
],
..SidecarResult::test_fixture()
};
let reversed = SidecarResult {
sysctls: vec![
"sysctl.c=3".to_string(),
"sysctl.b=2".to_string(),
"sysctl.a=1".to_string(),
],
kargs: vec![
"karg_gamma".to_string(),
"karg_beta".to_string(),
"karg_alpha".to_string(),
],
..SidecarResult::test_fixture()
};
assert_eq!(
sidecar_variant_hash(&forward),
sidecar_variant_hash(&reversed),
"reversed-order sysctls/kargs must hash identically — \
the hash sorts both collections lexically before \
folding bytes in, matching the set-determines-hash \
contract documented on `sidecar_variant_hash`. A \
regression that dropped the sort block would produce \
distinct hashes and duplicate sidecar files for the \
same semantic variant.",
);
let partial = SidecarResult {
sysctls: forward.sysctls.clone(),
kargs: reversed.kargs.clone(),
..SidecarResult::test_fixture()
};
assert_eq!(
sidecar_variant_hash(&forward),
sidecar_variant_hash(&partial),
"kargs-only reversal must still hash identically — \
partial revert (one of the two sorts dropped) must \
fail this assertion. Got distinct hashes for: \
sysctls={:?}, kargs={:?} vs sysctls={:?}, kargs={:?}",
forward.sysctls,
forward.kargs,
partial.sysctls,
partial.kargs,
);
}
#[test]
fn write_skip_sidecar_variant_hash_is_order_invariant_for_active_flags() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__skipflagorder_test__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let forward = vec!["llc".to_string(), "steal".to_string()];
let reversed = vec!["steal".to_string(), "llc".to_string()];
write_skip_sidecar(&entry, &forward).unwrap();
write_skip_sidecar(&entry, &reversed).unwrap();
let paths = find_sidecars_by_prefix(tmp, "__skipflagorder_test__-");
assert_eq!(
paths.len(),
1,
"reversed-order skip-sidecar writes of the same flag \
SET must collapse to a single canonical filename \
(overwrite); got {paths:?}. If this fails with \
`paths.len() == 2`, canonicalization was removed from \
`write_skip_sidecar` even if the run-path test above \
still passes — apply `canonicalize_active_flags` in \
both write sites, not just one.",
);
let path = &paths[0];
let data = std::fs::read_to_string(path).expect("read canonical skip sidecar");
let loaded: SidecarResult =
serde_json::from_str(&data).expect("deserialize canonical skip sidecar");
assert_eq!(
loaded.active_flags,
vec!["llc".to_string(), "steal".to_string()],
"on-disk active_flags of a skip sidecar must be sorted \
in `scenario::flags::ALL` positional order; got: {:?}",
loaded.active_flags,
);
}
#[test]
fn canonicalize_active_flags_orders_unknown_lexically_after_known() {
let input = vec![
"zzz_unknown".to_string(),
"llc".to_string(),
"aaa_unknown".to_string(),
];
let got = canonicalize_active_flags(&input);
assert_eq!(
got,
vec![
"llc".to_string(),
"aaa_unknown".to_string(),
"zzz_unknown".to_string(),
],
"known flags must sort first by ALL position, unknown \
flags must sort lexically after; got: {got:?}",
);
let reversed: Vec<String> = input.into_iter().rev().collect();
let got_rev = canonicalize_active_flags(&reversed);
assert_eq!(
got_rev, got,
"reversed input must canonicalize to the same output; \
got: {got_rev:?}, expected: {got:?}",
);
}
#[test]
fn write_sidecar_variant_hash_distinguishes_work_types() {
let _lock = lock_env();
let tmp_dir = tempfile::TempDir::new().unwrap();
let tmp = tmp_dir.path();
let _env_sidecar = EnvVarGuard::set("KTSTR_SIDECAR_DIR", tmp);
fn dummy(_ctx: &Ctx) -> Result<AssertResult> {
Ok(AssertResult::pass())
}
let entry = KtstrTestEntry {
name: "__variant_test__",
func: dummy,
auto_repro: false,
..KtstrTestEntry::DEFAULT
};
let vm_result = crate::vmm::VmResult::test_fixture();
let ok = AssertResult::pass();
write_sidecar(&entry, &vm_result, &[], &ok, "SpinWait", &[], &[]).unwrap();
write_sidecar(&entry, &vm_result, &[], &ok, "YieldHeavy", &[], &[]).unwrap();
let paths = find_sidecars_by_prefix(tmp, "__variant_test__-");
assert_eq!(
paths.len(),
2,
"two work_type variants must produce two distinct files, got {paths:?}"
);
}
#[test]
fn sidecar_variant_hash_stability_populated() {
let sc = SidecarResult {
topology: "1n2l4c1t".to_string(),
scheduler: "scx-ktstr".to_string(),
payload: None,
work_type: "SpinWait".to_string(),
active_flags: vec!["llc".to_string(), "steal".to_string()],
sysctls: vec!["sysctl.kernel.sched_cfs_bandwidth_slice_us=1000".to_string()],
kargs: vec!["nosmt".to_string()],
..SidecarResult::test_fixture()
};
assert_eq!(
sidecar_variant_hash(&sc),
0x33d8d0cf741e3a06,
"sidecar_variant_hash output drifted — regenerate expected only if \
the wire format change is intentional and old sidecars are \
disposable (which they are per ktstr's pre-1.0 stance)",
);
}
#[test]
fn sidecar_variant_hash_stability_empty_collections() {
let sc = SidecarResult {
topology: "1n1l1c1t".to_string(),
scheduler: "eevdf".to_string(),
payload: None,
work_type: String::new(),
active_flags: Vec::new(),
sysctls: Vec::new(),
kargs: Vec::new(),
..SidecarResult::test_fixture()
};
assert_eq!(sidecar_variant_hash(&sc), 0x5aa363a3d9aacd98);
}
#[test]
fn sidecar_variant_hash_distinguishes_payload() {
let base = SidecarResult::test_fixture;
let none = base();
assert!(
none.payload.is_none(),
"fixture default for payload must remain None"
);
let fio = SidecarResult {
payload: Some("fio".to_string()),
..base()
};
let stress = SidecarResult {
payload: Some("stress-ng".to_string()),
..base()
};
let h_none = sidecar_variant_hash(&none);
let h_fio = sidecar_variant_hash(&fio);
let h_stress = sidecar_variant_hash(&stress);
assert_ne!(
h_none, h_fio,
"absent vs present payload must hash differently",
);
assert_ne!(
h_fio, h_stress,
"different payload names must hash differently",
);
}
#[test]
fn format_verifier_stats_empty() {
assert!(format_verifier_stats(&[]).is_empty());
}
#[test]
fn format_verifier_stats_no_data() {
let sc = SidecarResult::test_fixture();
assert!(format_verifier_stats(&[sc]).is_empty());
}
#[test]
fn format_verifier_stats_table() {
let sc = SidecarResult {
verifier_stats: vec![
crate::monitor::bpf_prog::ProgVerifierStats {
name: "dispatch".to_string(),
verified_insns: 50000,
},
crate::monitor::bpf_prog::ProgVerifierStats {
name: "enqueue".to_string(),
verified_insns: 30000,
},
],
..SidecarResult::test_fixture()
};
let result = format_verifier_stats(&[sc]);
assert!(result.contains("BPF VERIFIER STATS"));
assert!(result.contains("dispatch"));
assert!(result.contains("enqueue"));
assert!(result.contains("50000"));
assert!(result.contains("30000"));
assert!(result.contains("total verified insns: 80000"));
assert!(!result.contains("WARNING"));
}
#[test]
fn format_verifier_stats_warning() {
let sc = SidecarResult {
verifier_stats: vec![crate::monitor::bpf_prog::ProgVerifierStats {
name: "heavy".to_string(),
verified_insns: 800000,
}],
..SidecarResult::test_fixture()
};
let result = format_verifier_stats(&[sc]);
assert!(result.contains("WARNING"));
assert!(result.contains("heavy"));
assert!(result.contains("80.0%"));
}
#[test]
fn sidecar_verifier_stats_serde_roundtrip() {
let sc = SidecarResult {
verifier_stats: vec![crate::monitor::bpf_prog::ProgVerifierStats {
name: "init".to_string(),
verified_insns: 5000,
}],
..SidecarResult::test_fixture()
};
let json = serde_json::to_string(&sc).unwrap();
assert!(json.contains("verifier_stats"));
let loaded: SidecarResult = serde_json::from_str(&json).unwrap();
assert_eq!(loaded.verifier_stats.len(), 1);
assert_eq!(loaded.verifier_stats[0].name, "init");
assert_eq!(loaded.verifier_stats[0].verified_insns, 5000);
}
#[test]
fn sidecar_verifier_stats_empty_emits_as_empty_array() {
let sc = SidecarResult::test_fixture();
let json = serde_json::to_string(&sc).unwrap();
assert!(
json.contains("\"verifier_stats\":[]"),
"empty verifier_stats must emit as `\"verifier_stats\":[]`: {json}",
);
}
#[test]
fn format_verifier_stats_deduplicates() {
let vstats = vec![crate::monitor::bpf_prog::ProgVerifierStats {
name: "dispatch".to_string(),
verified_insns: 50000,
}];
let sc1 = SidecarResult {
verifier_stats: vstats.clone(),
..SidecarResult::test_fixture()
};
let sc2 = SidecarResult {
verifier_stats: vstats,
..SidecarResult::test_fixture()
};
let result = format_verifier_stats(&[sc1, sc2]);
assert!(result.contains("total verified insns: 50000"));
}
#[test]
fn scheduler_fingerprint_eevdf_empty_extras() {
let entry = KtstrTestEntry {
name: "eevdf_test",
..KtstrTestEntry::DEFAULT
};
let SchedulerFingerprint {
scheduler: name,
scheduler_commit: commit,
sysctls,
kargs,
} = scheduler_fingerprint(&entry);
assert_eq!(name, "eevdf");
assert!(
commit.is_none(),
"Eevdf variant has no userspace binary; \
scheduler_commit must be None. Got: {commit:?}",
);
assert!(sysctls.is_empty());
assert!(kargs.is_empty());
}
#[test]
fn scheduler_fingerprint_formats_sysctls_with_prefix() {
use super::super::entry::Sysctl;
static SYSCTLS: &[Sysctl] = &[
Sysctl::new("kernel.foo", "1"),
Sysctl::new("kernel.bar", "yes"),
];
static SCHED: super::super::entry::Scheduler =
super::super::entry::Scheduler::new("s").sysctls(SYSCTLS);
static SCHED_PAYLOAD: super::super::payload::Payload =
super::super::payload::Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "s_test",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let SchedulerFingerprint {
scheduler: name,
scheduler_commit: _,
sysctls,
kargs,
} = scheduler_fingerprint(&entry);
assert_eq!(name, "s");
assert_eq!(
sysctls,
vec![
"sysctl.kernel.foo=1".to_string(),
"sysctl.kernel.bar=yes".to_string(),
]
);
assert!(kargs.is_empty());
}
#[test]
fn scheduler_fingerprint_forwards_kargs_verbatim() {
static SCHED: super::super::entry::Scheduler =
super::super::entry::Scheduler::new("s").kargs(&["quiet", "splash"]);
static SCHED_PAYLOAD: super::super::payload::Payload =
super::super::payload::Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "s_test",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let SchedulerFingerprint {
scheduler: _,
scheduler_commit: _,
sysctls,
kargs,
} = scheduler_fingerprint(&entry);
assert_eq!(kargs, vec!["quiet".to_string(), "splash".to_string()]);
assert!(sysctls.is_empty());
}
#[test]
fn scheduler_fingerprint_uses_display_name_for_discover() {
use super::super::entry::SchedulerSpec;
static SCHED: super::super::entry::Scheduler =
super::super::entry::Scheduler::new("s").binary(SchedulerSpec::Discover("scx_relaxed"));
static SCHED_PAYLOAD: super::super::payload::Payload =
super::super::payload::Payload::from_scheduler(&SCHED);
let entry = KtstrTestEntry {
name: "rel_test",
scheduler: &SCHED_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let SchedulerFingerprint {
scheduler: name,
scheduler_commit: commit,
sysctls: _,
kargs: _,
} = scheduler_fingerprint(&entry);
assert_eq!(name, "s");
assert!(
commit.is_none(),
"Discover variant currently returns None via \
`SchedulerSpec::scheduler_commit` — \
`resolve_scheduler`'s cascade does not guarantee a \
fresh build, so there is no authoritative source for \
the scheduler binary's commit and `scheduler_commit` \
reports None honestly. Got: {commit:?}",
);
}
#[test]
fn scheduler_fingerprint_binary_payload_has_no_commit() {
static BINARY_PAYLOAD: super::super::payload::Payload =
super::super::payload::Payload::binary("bin_test", "some_binary");
let entry = KtstrTestEntry {
name: "bin_test",
scheduler: &BINARY_PAYLOAD,
..KtstrTestEntry::DEFAULT
};
let SchedulerFingerprint {
scheduler: name,
scheduler_commit: commit,
sysctls,
kargs,
} = scheduler_fingerprint(&entry);
assert_eq!(
name, "kernel_default",
"binary-kind payload must report the intent-level \
scheduler label; got: {name:?}",
);
assert!(
commit.is_none(),
"binary-kind payload has no scheduler binary at all — \
scheduler_commit must be None via the `and_then` \
short-circuit on `scheduler_binary() == None`. Got: \
{commit:?}",
);
assert!(
sysctls.is_empty(),
"binary-kind payload reports no sysctls; got: {sysctls:?}",
);
assert!(
kargs.is_empty(),
"binary-kind payload reports no kargs; got: {kargs:?}",
);
}
mod commits;