use std::path::PathBuf;
use std::process::Command;
use cargo_metadata::semver::Version;
use crate::kernel::{encode_kernel_list, resolve_kernel_set};
pub(crate) const TEST_SUB_ARGV: &[&str] = &["nextest", "run"];
pub(crate) const COVERAGE_SUB_ARGV: &[&str] = &["llvm-cov", "nextest"];
pub(crate) const LLVM_COV_SUB_ARGV: &[&str] = &["llvm-cov"];
pub(crate) fn profraw_inject_for(
sub_argv: &[&str],
existing_env: Option<std::ffi::OsString>,
) -> Option<PathBuf> {
if sub_argv != TEST_SUB_ARGV || existing_env.is_some() {
return None;
}
let dir = ktstr::test_support::profraw_target_dir();
Some(dir.join("default-%p-%m.profraw"))
}
fn prebuilt_blob_bin_envs(
busybox_path: Option<std::ffi::OsString>,
wprof_path: Option<std::ffi::OsString>,
) -> Vec<(&'static str, std::ffi::OsString)> {
let mut pairs = Vec::new();
if let Some(p) = busybox_path {
pairs.push(("KTSTR_BUSYBOX_BIN", p));
}
if let Some(p) = wprof_path {
pairs.push(("KTSTR_WPROF_BIN", p));
}
pairs
}
fn build_cargo_command(
sub_argv: &[&str],
release: bool,
profile: Option<&str>,
nextest_profile: Option<&str>,
no_perf_mode: bool,
no_skip_mode: bool,
args: &[String],
) -> Command {
let mut cmd = Command::new("cargo");
cmd.args(sub_argv);
if release {
cmd.args(["--cargo-profile", "release"]);
}
if let Some(np) = nextest_profile {
cmd.args(["--profile", np]);
}
cmd.args(args);
if no_perf_mode {
cmd.env(ktstr::KTSTR_NO_PERF_MODE_ENV, "1");
}
if no_skip_mode {
cmd.env(ktstr::KTSTR_NO_SKIP_MODE_ENV, "1");
}
if let Some(p) = profile {
cmd.env(ktstr::KTSTR_SCHEDULER_PROFILE_ENV, p);
}
cmd
}
fn kernel_set_or_bail(
kernel: &[String],
include_eol: bool,
) -> Result<Vec<(String, PathBuf)>, String> {
if kernel.is_empty() {
return Ok(Vec::new());
}
let resolved = resolve_kernel_set(kernel, include_eol)?;
if resolved.is_empty() {
return Err(
"--kernel: every supplied value parsed to empty / whitespace; \
omit the flag for auto-discovery, or supply a kernel \
identifier"
.to_string(),
);
}
Ok(resolved)
}
#[allow(clippy::too_many_arguments)]
fn run_cargo_sub(
sub_argv: &[&str],
label: &str,
kernel: Vec<String>,
no_perf_mode: bool,
no_skip_mode: bool,
release: bool,
profile: Option<String>,
nextest_profile: Option<String>,
include_eol: bool,
args: Vec<String>,
) -> Result<(), String> {
let mut cmd = build_cargo_command(
sub_argv,
release,
profile.as_deref(),
nextest_profile.as_deref(),
no_perf_mode,
no_skip_mode,
&args,
);
for (var, val) in prebuilt_blob_bin_envs(
std::env::var_os(ktstr::KTSTR_BUSYBOX_PATH_ENV),
std::env::var_os("KTSTR_WPROF_PATH"),
) {
cmd.env(var, val);
}
if let Some(pat) = profraw_inject_for(sub_argv, std::env::var_os("LLVM_PROFILE_FILE")) {
cmd.env("LLVM_PROFILE_FILE", pat);
}
let resolved = kernel_set_or_bail(&kernel, include_eol)?;
if !resolved.is_empty() {
let first_dir = &resolved[0].1;
tracing::debug!("cargo ktstr: using kernel {}", first_dir.display());
cmd.env(ktstr::KTSTR_KERNEL_ENV, first_dir);
cmd.env(ktstr::KTSTR_ORCHESTRATED_ENV, "1");
let commit_map = resolved
.iter()
.filter_map(|(_, dir)| {
let raw = dir.display().to_string();
let commit = ktstr::test_support::source_dir_for(&raw)
.and_then(|src| ktstr::test_support::detect_kernel_commit(&src))?;
Some(format!("{raw}={commit}"))
})
.collect::<Vec<_>>()
.join(";");
if !commit_map.is_empty() {
cmd.env(ktstr::KTSTR_KERNEL_COMMIT_ENV, commit_map);
}
if resolved.len() > 1 {
let encoded = encode_kernel_list(&resolved)?;
eprintln!(
"cargo ktstr: fanning gauntlet across {n} kernels",
n = resolved.len(),
);
cmd.env(ktstr::KTSTR_KERNEL_LIST_ENV, encoded);
}
}
let target_dir_path = resolve_target_dir();
precompute_cast_cache(&target_dir_path);
if let Some(anchor_path) = generate_btf_anchor(&target_dir_path, release) {
let existing = std::env::var("BPF_EXTRA_CFLAGS_PRE_INCL").unwrap_or_default();
let inject = format!("-include {} {existing}", anchor_path.display());
cmd.env("BPF_EXTRA_CFLAGS_PRE_INCL", inject.trim());
eprintln!("cargo ktstr: BTF type anchor at {}", anchor_path.display());
}
tracing::debug!("cargo ktstr: running {label}");
let run_start = std::time::SystemTime::now();
if let Ok(d) = run_start.duration_since(std::time::UNIX_EPOCH) {
cmd.env(ktstr::KTSTR_RUN_EPOCH_ENV, d.as_nanos().to_string());
}
let interrupt_guard = crate::interrupt::InterruptGuard::install();
let status = cmd
.status()
.map_err(|e| format!("spawn cargo {}: {e}", sub_argv.join(" ")))?;
cleanup_shm();
let runs_root = ktstr::test_support::runs_root();
let footer = ktstr::test_support::format_run_artifact_footer(&runs_root, run_start);
if !footer.is_empty() {
eprint!("{footer}");
}
let caught = interrupt_guard.interrupted();
drop(interrupt_guard);
if let Some(sig) = caught {
crate::interrupt::reraise(sig);
}
if !status.success() {
eprintln!(
"\ncargo ktstr: nextest reported failures (see its summary above); \
per-test artifacts for failures that produced them are listed above. \
Artifacts under {}.",
runs_root.display(),
);
}
if status.success() {
Ok(())
} else {
Err(format!(
"cargo {} exited with {}",
sub_argv.join(" "),
status
.code()
.map_or("signal".to_string(), |c| c.to_string()),
))
}
}
fn precompute_cast_cache(target_dir: &std::path::Path) {
let binaries = collect_scheduler_binaries(target_dir);
if binaries.is_empty() {
return;
}
eprintln!(
"cargo ktstr: precomputing cast analysis for {} scheduler binaries",
binaries.len()
);
for binary in binaries {
let path = binary.clone();
std::thread::spawn(move || {
ktstr::precompute_cast_analysis(&path);
});
}
}
fn collect_scheduler_binaries(target_dir: &std::path::Path) -> Vec<std::path::PathBuf> {
let mut binaries = Vec::new();
for profile in ["debug", "release"] {
let dir = target_dir.join(profile);
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let name = entry.file_name();
let Some(name_str) = name.to_str() else {
continue;
};
if name_str.starts_with("scx_") && !name_str.contains('.') {
let path = entry.path();
if path.is_file() {
binaries.push(path);
}
}
}
}
binaries
}
fn generate_btf_anchor(target_dir: &std::path::Path, release: bool) -> Option<std::path::PathBuf> {
let anchor_path = target_dir.join("ktstr_btf_anchor.h");
let profile = if release { "release" } else { "debug" };
let build_root = target_dir.join(profile).join("build");
let mut bpf_object_dirs: Vec<PathBuf> = Vec::new();
if let Ok(entries) = std::fs::read_dir(&build_root) {
for entry in entries.flatten() {
let out = entry.path().join("out");
if out.join("bpf.bpf.o").is_file() {
bpf_object_dirs.push(out);
}
}
}
if bpf_object_dirs.is_empty() {
return None;
}
bpf_object_dirs.sort_by_key(|d| {
std::cmp::Reverse(
std::fs::read_dir(d)
.map(|r| {
r.flatten()
.filter(|e| {
e.file_name()
.to_str()
.is_some_and(|n| n.ends_with(".bpf.o"))
})
.count()
})
.unwrap_or(0),
)
});
let bpf_object_dir = &bpf_object_dirs[0];
let mut cflags: Vec<String> = Vec::new();
if let Ok(base) = std::env::var("BPF_BASE_CFLAGS") {
cflags.extend(base.split_whitespace().map(String::from));
} else {
cflags.extend(["-g", "-O2"].iter().map(|s| s.to_string()));
}
if let Ok(pre) = std::env::var("BPF_EXTRA_CFLAGS_PRE_INCL") {
cflags.extend(pre.split_whitespace().map(String::from));
}
if let Ok(entries) = std::fs::read_dir(&build_root) {
for entry in entries.flatten() {
let bpf_h = entry.path().join("out/scx_utils-bpf_h");
if bpf_h.is_dir() {
cflags.push(format!("-I{}", bpf_h.display()));
}
}
}
if let Ok(post) = std::env::var("BPF_EXTRA_CFLAGS_POST_INCL") {
cflags.extend(post.split_whitespace().map(String::from));
}
let clang = std::env::var("BPF_CLANG").unwrap_or_else(|_| "clang".to_string());
crate::btf_catalog::generate_btf_anchor(bpf_object_dir, &clang, &cflags, &anchor_path)
}
fn resolve_target_dir() -> std::path::PathBuf {
if let Ok(d) = std::env::var("CARGO_TARGET_DIR") {
return std::path::PathBuf::from(d);
}
if let Ok(output) = Command::new("cargo")
.args(["metadata", "--format-version=1", "--no-deps"])
.output()
&& output.status.success()
&& let Ok(v) = serde_json::from_slice::<serde_json::Value>(&output.stdout)
&& let Some(dir) = v["target_directory"].as_str()
{
return std::path::PathBuf::from(dir);
}
std::path::PathBuf::from("target")
}
pub(crate) fn install_runs_root_env() {
if std::env::var_os(ktstr::KTSTR_RUNS_ROOT_ENV)
.filter(|v| !v.is_empty())
.is_some()
{
return;
}
let runs_root = resolve_target_dir().join("ktstr");
let runs_root = if runs_root.is_absolute() {
runs_root
} else {
let Ok(cwd) = std::env::current_dir() else {
return;
};
cwd.join(runs_root)
};
unsafe {
std::env::set_var(ktstr::KTSTR_RUNS_ROOT_ENV, &runs_root);
}
}
fn cleanup_shm() {
let Ok(dir) = std::fs::read_dir("/dev/shm") else {
return;
};
for entry in dir.flatten() {
let name = entry.file_name();
let Some(name_str) = name.to_str() else {
continue;
};
if !name_str.starts_with("ktstr-base-")
&& !name_str.starts_with("ktstr-lz4-")
&& !name_str.starts_with("ktstr-gz-")
{
continue;
}
let shm_name = format!("/{name_str}");
let Ok(fd) = rustix::shm::open(
shm_name.as_str(),
rustix::shm::OFlags::RDONLY,
rustix::fs::Mode::empty(),
) else {
continue;
};
if rustix::fs::flock(&fd, rustix::fs::FlockOperation::NonBlockingLockExclusive).is_err() {
continue;
}
let _ = rustix::shm::unlink(shm_name.as_str());
let _ = rustix::fs::flock(&fd, rustix::fs::FlockOperation::Unlock);
}
}
#[derive(Debug, PartialEq, Eq)]
enum VersionGuard {
Ok,
Warn(String),
Error(String),
}
fn version_guard(cli: &Version, test: &Version) -> VersionGuard {
use std::cmp::Ordering;
match test.cmp_precedence(cli) {
Ordering::Equal => VersionGuard::Ok,
Ordering::Less => VersionGuard::Warn(format!(
"the test was built against ktstr {test} but the cargo-ktstr CLI \
is {cli} — version skew. Align them (bump the test's ktstr \
dependency, or use a matching CLI) to avoid surprises."
)),
Ordering::Greater => VersionGuard::Error(format!(
"the test was built against ktstr {test} but the cargo-ktstr CLI \
is {cli} — the CLI is older than the ktstr the test depends on \
and cannot drive it. Upgrade the CLI: `cargo install ktstr` \
(or `cargo install --path .` in the ktstr repo)."
)),
}
}
fn resolved_ktstr_version(meta: &cargo_metadata::Metadata) -> Option<&Version> {
let resolve = meta.resolve.as_ref()?;
let root_ids: Vec<&cargo_metadata::PackageId> = match &resolve.root {
Some(root) => vec![root],
None => meta.workspace_members.iter().collect(),
};
for root_id in root_ids {
let Some(root_pkg) = meta.packages.iter().find(|p| &p.id == root_id) else {
continue;
};
if root_pkg.name == "ktstr" {
return Some(&root_pkg.version);
}
let Some(node) = resolve.nodes.iter().find(|n| &n.id == root_id) else {
continue;
};
for dep in &node.deps {
let Some(dep_pkg) = meta.packages.iter().find(|p| p.id == dep.pkg) else {
continue;
};
if dep_pkg.name != "ktstr" {
continue;
}
let linked = dep.dep_kinds.is_empty()
|| dep.dep_kinds.iter().any(|dk| {
matches!(
dk.kind,
cargo_metadata::DependencyKind::Normal
| cargo_metadata::DependencyKind::Development
)
});
if linked {
return Some(&dep_pkg.version);
}
}
}
None
}
fn check_ktstr_version_compat() -> Result<(), String> {
let cli = Version::parse(env!("CARGO_PKG_VERSION"))
.expect("cargo-ktstr's own CARGO_PKG_VERSION is valid semver");
let meta = match cargo_metadata::MetadataCommand::new().exec() {
Ok(m) => m,
Err(e) => {
tracing::warn!(error = %e, "ktstr version guard: cargo metadata failed; skipping");
return Ok(());
}
};
let Some(test) = resolved_ktstr_version(&meta) else {
return Ok(());
};
match version_guard(&cli, test) {
VersionGuard::Ok => {}
VersionGuard::Warn(msg) => eprintln!("cargo ktstr: warning: {msg}"),
VersionGuard::Error(msg) => return Err(msg),
}
Ok(())
}
fn extract_nextest_filtersets(args: Vec<String>) -> (Vec<String>, Vec<String>) {
let mut filters = Vec::new();
let mut rest = Vec::new();
let mut it = args.into_iter();
while let Some(tok) = it.next() {
if tok == "-E" || tok == "--filterset" || tok == "--filter-expr" {
if let Some(val) = it.next() {
filters.push(val);
}
} else if let Some(val) = tok
.strip_prefix("--filterset=")
.or_else(|| tok.strip_prefix("--filter-expr="))
{
filters.push(val.to_string());
} else if let Some(rest_short) = tok.strip_prefix("-E") {
filters.push(
rest_short
.strip_prefix('=')
.unwrap_or(rest_short)
.to_string(),
);
} else {
rest.push(tok);
}
}
(filters, rest)
}
fn compose_relevant_filter(args: Vec<String>, relevant: &str) -> Vec<String> {
let (user_filtersets, mut rest) = extract_nextest_filtersets(args);
let combined = if user_filtersets.is_empty() {
relevant.to_string()
} else {
let union = user_filtersets
.iter()
.map(|f| format!("({f})"))
.collect::<Vec<_>>()
.join(" | ");
format!("({relevant}) & ({union})")
};
rest.push("-E".to_string());
rest.push(combined);
rest
}
fn apply_relevant_narrowing(
args: Vec<String>,
relevant: bool,
base: Option<String>,
base_ref: Option<String>,
default_branch: String,
) -> Result<Vec<String>, String> {
if !relevant {
return Ok(args);
}
match crate::affected::relevant_test_filter(
base.as_deref(),
base_ref.as_deref(),
&default_branch,
) {
Ok(Some(expr)) => Ok(compose_relevant_filter(args, &expr)),
Ok(None) => Ok(args),
Err(e) => Err(format!("compute --relevant test set: {e:#}")),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_test(
kernel: Vec<String>,
no_perf_mode: bool,
no_skip_mode: bool,
release: bool,
profile: Option<String>,
nextest_profile: Option<String>,
include_eol: bool,
relevant: bool,
base: Option<String>,
base_ref: Option<String>,
default_branch: String,
args: Vec<String>,
) -> Result<(), String> {
ktstr::cli::check_kvm().map_err(|e| format!("{e:#}"))?;
ktstr::cli::check_tools(&["cargo-nextest"]).map_err(|e| format!("{e:#}"))?;
check_ktstr_version_compat()?;
let args = apply_relevant_narrowing(args, relevant, base, base_ref, default_branch)?;
run_cargo_sub(
TEST_SUB_ARGV,
"tests",
kernel,
no_perf_mode,
no_skip_mode,
release,
profile,
nextest_profile,
include_eol,
args,
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn run_coverage(
kernel: Vec<String>,
no_perf_mode: bool,
no_skip_mode: bool,
release: bool,
profile: Option<String>,
nextest_profile: Option<String>,
include_eol: bool,
relevant: bool,
base: Option<String>,
base_ref: Option<String>,
default_branch: String,
args: Vec<String>,
) -> Result<(), String> {
ktstr::cli::check_kvm().map_err(|e| format!("{e:#}"))?;
ktstr::cli::check_tools(&["cargo-nextest", "cargo-llvm-cov"]).map_err(|e| format!("{e:#}"))?;
check_ktstr_version_compat()?;
let args = apply_relevant_narrowing(args, relevant, base, base_ref, default_branch)?;
run_cargo_sub(
COVERAGE_SUB_ARGV,
"coverage",
kernel,
no_perf_mode,
no_skip_mode,
release,
profile,
nextest_profile,
include_eol,
args,
)
}
pub(crate) fn run_llvm_cov(
kernel: Vec<String>,
no_perf_mode: bool,
no_skip_mode: bool,
include_eol: bool,
args: Vec<String>,
) -> Result<(), String> {
run_cargo_sub(
LLVM_COV_SUB_ARGV,
"llvm-cov",
kernel,
no_perf_mode,
no_skip_mode,
false,
None,
None,
include_eol,
args,
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::OsString;
use std::sync::{Mutex, MutexGuard, OnceLock};
fn v(s: &str) -> Version {
Version::parse(s).expect("test version literal is valid semver")
}
#[test]
fn version_guard_equal_is_ok() {
assert_eq!(version_guard(&v("0.19.0"), &v("0.19.0")), VersionGuard::Ok);
}
#[test]
fn version_guard_test_older_warns() {
assert!(matches!(
version_guard(&v("0.19.0"), &v("0.18.0")),
VersionGuard::Warn(_)
));
}
#[test]
fn version_guard_test_newer_errors() {
assert!(matches!(
version_guard(&v("0.19.0"), &v("0.20.0")),
VersionGuard::Error(_)
));
}
#[test]
fn version_guard_patch_delta_full_version_compare() {
assert!(matches!(
version_guard(&v("0.19.0"), &v("0.19.1")),
VersionGuard::Error(_)
));
assert!(matches!(
version_guard(&v("0.19.1"), &v("0.19.0")),
VersionGuard::Warn(_)
));
}
#[test]
fn version_guard_ignores_build_metadata() {
assert_eq!(
version_guard(&v("0.19.0"), &v("0.19.0+abc")),
VersionGuard::Ok,
);
assert_eq!(
version_guard(&v("0.19.0+abc"), &v("0.19.0")),
VersionGuard::Ok,
);
}
fn strs(v: &[&str]) -> Vec<String> {
v.iter().map(|s| s.to_string()).collect()
}
#[test]
fn extract_filtersets_all_spellings() {
let (filters, rest) = extract_nextest_filtersets(strs(&[
"-E",
"test(a)",
"--filterset",
"test(b)",
"--filter-expr=test(c)",
"-E=test(d)",
"-Etest(e)",
"--filterset=test(f)",
"positional_name",
"--features",
"integration",
]));
assert_eq!(
filters,
strs(&[
"test(a)", "test(b)", "test(c)", "test(d)", "test(e)", "test(f)",
]),
);
assert_eq!(
rest,
strs(&["positional_name", "--features", "integration"])
);
}
#[test]
fn compose_relevant_no_user_filterset() {
let out = compose_relevant_filter(strs(&["--features", "x"]), "test(r1) | test(r2)");
assert_eq!(out, strs(&["--features", "x", "-E", "test(r1) | test(r2)"]),);
}
#[test]
fn compose_relevant_intersects_user_filtersets() {
let out = compose_relevant_filter(
strs(&[
"-E",
"test(u1)",
"--features",
"x",
"--filterset",
"test(u2)",
]),
"test(r)",
);
assert_eq!(
out,
strs(&[
"--features",
"x",
"-E",
"(test(r)) & ((test(u1)) | (test(u2)))",
]),
);
}
#[test]
fn compose_relevant_none_expression() {
let out = compose_relevant_filter(Vec::new(), "none()");
assert_eq!(out, strs(&["-E", "none()"]));
}
#[test]
fn apply_relevant_narrowing_off_is_noop() {
let args = strs(&["-E", "test(x)", "--features", "y"]);
let out = apply_relevant_narrowing(args.clone(), false, None, None, "main".to_string())
.expect("no-op path never errors");
assert_eq!(out, args);
}
fn pkg_json(name: &str, version: &str, id: &str, source: &str) -> String {
format!(
r#"{{"name":"{name}","version":"{version}","id":"{id}","source":{source},"description":null,"dependencies":[],"license":null,"license_file":null,"targets":[],"features":{{}},"manifest_path":"/w/{name}/Cargo.toml","readme":null,"repository":null,"homepage":null,"documentation":null,"links":null,"publish":null,"default_run":null}}"#
)
}
#[test]
fn resolved_ktstr_version_picks_linked_not_max() {
let crates_io = r#""registry+https://github.com/rust-lang/crates.io-index""#;
let root = "userproj 0.1.0 (path+file:///w/userproj)";
let k016 = "ktstr 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)";
let k020 = "ktstr 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)";
let other = "otherdep 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)";
let json = format!(
r#"{{
"packages":[{up},{a},{b},{o}],
"workspace_members":["{root}"],
"resolve":{{
"root":"{root}",
"nodes":[
{{"id":"{root}","deps":[{{"name":"ktstr","pkg":"{k016}","dep_kinds":[{{"kind":null,"target":null}}]}},{{"name":"otherdep","pkg":"{other}","dep_kinds":[{{"kind":null,"target":null}}]}}],"dependencies":["{k016}","{other}"],"features":[]}},
{{"id":"{other}","deps":[{{"name":"ktstr","pkg":"{k020}","dep_kinds":[{{"kind":null,"target":null}}]}}],"dependencies":["{k020}"],"features":[]}},
{{"id":"{k016}","deps":[],"dependencies":[],"features":[]}},
{{"id":"{k020}","deps":[],"dependencies":[],"features":[]}}
]
}},
"workspace_root":"/w","target_directory":"/w/target","version":1
}}"#,
up = pkg_json("userproj", "0.1.0", root, "null"),
a = pkg_json("ktstr", "0.16.0", k016, crates_io),
b = pkg_json("ktstr", "0.20.0", k020, crates_io),
o = pkg_json("otherdep", "0.1.0", other, crates_io),
);
let meta: cargo_metadata::Metadata =
serde_json::from_str(&json).expect("fixture deserializes as cargo metadata");
assert_eq!(
resolved_ktstr_version(&meta),
Some(&v("0.16.0")),
"must pick the LINKED ktstr (root's direct dep), not the .max() (0.20.0)",
);
}
#[test]
fn resolved_ktstr_version_root_is_ktstr() {
let root = "ktstr 0.19.0 (path+file:///w)";
let json = format!(
r#"{{
"packages":[{k}],
"workspace_members":["{root}"],
"resolve":{{"root":"{root}","nodes":[{{"id":"{root}","deps":[],"dependencies":[],"features":[]}}]}},
"workspace_root":"/w","target_directory":"/w/target","version":1
}}"#,
k = pkg_json("ktstr", "0.19.0", root, "null"),
);
let meta: cargo_metadata::Metadata =
serde_json::from_str(&json).expect("fixture deserializes");
assert_eq!(resolved_ktstr_version(&meta), Some(&v("0.19.0")));
}
#[test]
fn resolved_ktstr_version_skips_build_only_edge() {
let crates_io = r#""registry+https://github.com/rust-lang/crates.io-index""#;
let root = "userproj 0.1.0 (path+file:///w/userproj)";
let kb = "ktstr 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)";
let json = format!(
r#"{{
"packages":[{up},{k}],
"workspace_members":["{root}"],
"resolve":{{
"root":"{root}",
"nodes":[
{{"id":"{root}","deps":[{{"name":"ktstr","pkg":"{kb}","dep_kinds":[{{"kind":"build","target":null}}]}}],"dependencies":["{kb}"],"features":[]}},
{{"id":"{kb}","deps":[],"dependencies":[],"features":[]}}
]
}},
"workspace_root":"/w","target_directory":"/w/target","version":1
}}"#,
up = pkg_json("userproj", "0.1.0", root, "null"),
k = pkg_json("ktstr", "0.20.0", kb, crates_io),
);
let meta: cargo_metadata::Metadata =
serde_json::from_str(&json).expect("fixture deserializes");
assert_eq!(
resolved_ktstr_version(&meta),
None,
"a build-only ktstr edge is not linked by test/bin targets — skip, not abort",
);
}
#[test]
fn resolved_ktstr_version_virtual_workspace_walks_members() {
let crates_io = r#""registry+https://github.com/rust-lang/crates.io-index""#;
let member = "memberproj 0.1.0 (path+file:///w/memberproj)";
let k = "ktstr 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)";
let json = format!(
r#"{{
"packages":[{m},{kp}],
"workspace_members":["{member}"],
"resolve":{{
"root":null,
"nodes":[
{{"id":"{member}","deps":[{{"name":"ktstr","pkg":"{k}","dep_kinds":[{{"kind":null,"target":null}}]}}],"dependencies":["{k}"],"features":[]}},
{{"id":"{k}","deps":[],"dependencies":[],"features":[]}}
]
}},
"workspace_root":"/w","target_directory":"/w/target","version":1
}}"#,
m = pkg_json("memberproj", "0.1.0", member, "null"),
kp = pkg_json("ktstr", "0.17.0", k, crates_io),
);
let meta: cargo_metadata::Metadata =
serde_json::from_str(&json).expect("fixture deserializes");
assert_eq!(resolved_ktstr_version(&meta), Some(&v("0.17.0")));
}
fn env_lock() -> MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
struct EnvVar {
key: &'static str,
prev: Option<OsString>,
}
impl EnvVar {
fn set(key: &'static str, val: impl AsRef<std::ffi::OsStr>) -> Self {
let prev = std::env::var_os(key);
unsafe { std::env::set_var(key, val) };
Self { key, prev }
}
fn remove(key: &'static str) -> Self {
let prev = std::env::var_os(key);
unsafe { std::env::remove_var(key) };
Self { key, prev }
}
}
impl Drop for EnvVar {
fn drop(&mut self) {
unsafe {
match &self.prev {
Some(v) => std::env::set_var(self.key, v),
None => std::env::remove_var(self.key),
}
}
}
}
#[test]
fn install_runs_root_env_stamps_absolute_target_subdir() {
let _lock = env_lock();
let tmp = tempfile::TempDir::new().unwrap();
let _g0 = EnvVar::remove(ktstr::KTSTR_RUNS_ROOT_ENV);
let _g1 = EnvVar::set("CARGO_TARGET_DIR", tmp.path());
install_runs_root_env();
assert_eq!(
std::env::var_os(ktstr::KTSTR_RUNS_ROOT_ENV).map(std::path::PathBuf::from),
Some(tmp.path().join("ktstr")),
"orchestrator must export the absolute {{target}}/ktstr so the \
child writers and the footer reader agree on one dir",
);
}
#[test]
fn install_runs_root_env_is_idempotent_when_already_set() {
let _lock = env_lock();
let tmp = tempfile::TempDir::new().unwrap();
let preset = tmp.path().join("operator-chosen-root");
let _g0 = EnvVar::set(ktstr::KTSTR_RUNS_ROOT_ENV, &preset);
let _g1 = EnvVar::set("CARGO_TARGET_DIR", tmp.path());
install_runs_root_env();
assert_eq!(
std::env::var_os(ktstr::KTSTR_RUNS_ROOT_ENV).map(std::path::PathBuf::from),
Some(preset),
"a pre-set KTSTR_RUNS_ROOT must survive install (no clobber)",
);
}
#[test]
fn install_runs_root_env_absolutizes_relative_target() {
let _lock = env_lock();
let _g0 = EnvVar::remove(ktstr::KTSTR_RUNS_ROOT_ENV);
let _g1 = EnvVar::set("CARGO_TARGET_DIR", "relative/target");
install_runs_root_env();
let got = std::env::var_os(ktstr::KTSTR_RUNS_ROOT_ENV).map(std::path::PathBuf::from);
let expected = std::env::current_dir()
.unwrap()
.join("relative/target/ktstr");
assert_eq!(
got,
Some(expected),
"a relative CARGO_TARGET_DIR must be anchored to the orchestrator cwd \
and exported absolute, not skipped",
);
}
#[test]
fn collect_scheduler_binaries_filters_to_bare_scx_executables() {
let tmp = tempfile::TempDir::new().unwrap();
assert!(collect_scheduler_binaries(tmp.path()).is_empty());
let debug = tmp.path().join("debug");
std::fs::create_dir(&debug).unwrap();
std::fs::write(debug.join("scx_ktstr"), b"x").unwrap(); std::fs::write(debug.join("scx_ktstr.d"), b"x").unwrap(); std::fs::write(debug.join("ktstr"), b"x").unwrap(); std::fs::create_dir(debug.join("scx_subdir")).unwrap();
assert_eq!(
collect_scheduler_binaries(tmp.path()),
vec![debug.join("scx_ktstr")],
"only the bare scx_* executable is collected",
);
}
#[test]
fn cargo_sub_argv_constants_are_pinned() {
assert_eq!(TEST_SUB_ARGV, &["nextest", "run"]);
assert_eq!(COVERAGE_SUB_ARGV, &["llvm-cov", "nextest"]);
assert_eq!(LLVM_COV_SUB_ARGV, &["llvm-cov"]);
}
#[test]
fn profraw_inject_for_test_path_returns_pattern() {
let pat = profraw_inject_for(TEST_SUB_ARGV, None)
.expect("test path without LLVM_PROFILE_FILE must inject");
assert!(
pat.ends_with("default-%p-%m.profraw"),
"injected pattern must end with default-%%p-%%m.profraw, got {}",
pat.display(),
);
assert_ne!(
pat.as_os_str(),
"default-%p-%m.profraw",
"pattern must be absolute (carry a target dir prefix), \
not bare so the LLVM runtime never falls back to cwd",
);
}
#[test]
fn profraw_inject_for_coverage_path_skips() {
assert!(
profraw_inject_for(COVERAGE_SUB_ARGV, None).is_none(),
"coverage path must not inject — cargo-llvm-cov owns LLVM_PROFILE_FILE",
);
}
#[test]
fn profraw_inject_for_llvm_cov_path_skips() {
assert!(
profraw_inject_for(LLVM_COV_SUB_ARGV, None).is_none(),
"llvm-cov passthrough path must not inject — user owns env decisions",
);
}
#[test]
fn profraw_inject_for_respects_operator_override() {
let existing = std::ffi::OsString::from("/tmp/operator-pinned-%p.profraw");
assert!(
profraw_inject_for(TEST_SUB_ARGV, Some(existing)).is_none(),
"an operator-set LLVM_PROFILE_FILE must not be overridden",
);
}
#[test]
fn prebuilt_blob_bin_envs_sets_present_paths() {
let pairs = prebuilt_blob_bin_envs(
Some(std::ffi::OsString::from("/run/bb")),
Some(std::ffi::OsString::from("/run/wp")),
);
assert_eq!(
pairs,
vec![
("KTSTR_BUSYBOX_BIN", std::ffi::OsString::from("/run/bb")),
("KTSTR_WPROF_BIN", std::ffi::OsString::from("/run/wp")),
],
);
}
#[test]
fn prebuilt_blob_bin_envs_omits_absent_paths() {
assert!(
prebuilt_blob_bin_envs(None, None).is_empty(),
"no source paths → no env pairs",
);
assert_eq!(
prebuilt_blob_bin_envs(Some(std::ffi::OsString::from("/run/bb")), None),
vec![("KTSTR_BUSYBOX_BIN", std::ffi::OsString::from("/run/bb"))],
"busybox present, wprof absent → only the busybox pair",
);
}
fn cmd_env_map(
cmd: &Command,
) -> std::collections::BTreeMap<std::ffi::OsString, Option<std::ffi::OsString>> {
cmd.get_envs()
.map(|(k, v)| (k.to_os_string(), v.map(|v| v.to_os_string())))
.collect()
}
#[test]
fn build_cargo_command_release_prepends_profile_before_user_args() {
let cmd = build_cargo_command(
TEST_SUB_ARGV,
true,
None,
None,
false,
false,
&["-E".to_string(), "test(foo)".to_string()],
);
let argv: Vec<&std::ffi::OsStr> = cmd.get_args().collect();
assert_eq!(
argv,
[
"nextest",
"run",
"--cargo-profile",
"release",
"-E",
"test(foo)"
]
.map(std::ffi::OsStr::new),
);
}
#[test]
fn build_cargo_command_no_release_omits_profile_flag() {
let cmd = build_cargo_command(
LLVM_COV_SUB_ARGV,
false,
None,
None,
false,
false,
&["report".to_string()],
);
let argv: Vec<&std::ffi::OsStr> = cmd.get_args().collect();
assert_eq!(argv, ["llvm-cov", "report"].map(std::ffi::OsStr::new));
}
#[test]
fn build_cargo_command_perf_and_skip_env_gates() {
let cmd = build_cargo_command(TEST_SUB_ARGV, false, None, None, true, false, &[]);
let map = cmd_env_map(&cmd);
assert_eq!(
map.get(std::ffi::OsStr::new(ktstr::KTSTR_NO_PERF_MODE_ENV)),
Some(&Some(std::ffi::OsString::from("1"))),
);
assert!(!map.contains_key(std::ffi::OsStr::new(ktstr::KTSTR_NO_SKIP_MODE_ENV)));
let cmd = build_cargo_command(TEST_SUB_ARGV, false, None, None, false, true, &[]);
let map = cmd_env_map(&cmd);
assert_eq!(
map.get(std::ffi::OsStr::new(ktstr::KTSTR_NO_SKIP_MODE_ENV)),
Some(&Some(std::ffi::OsString::from("1"))),
);
assert!(!map.contains_key(std::ffi::OsStr::new(ktstr::KTSTR_NO_PERF_MODE_ENV)));
}
#[test]
fn build_cargo_command_scheduler_profile_wired_from_flag() {
let cmd = build_cargo_command(TEST_SUB_ARGV, false, Some("dev"), None, false, false, &[]);
let map = cmd_env_map(&cmd);
assert_eq!(
map.get(std::ffi::OsStr::new(ktstr::KTSTR_SCHEDULER_PROFILE_ENV)),
Some(&Some(std::ffi::OsString::from("dev"))),
);
let cmd = build_cargo_command(TEST_SUB_ARGV, false, None, None, false, false, &[]);
let map = cmd_env_map(&cmd);
assert!(!map.contains_key(std::ffi::OsStr::new(ktstr::KTSTR_SCHEDULER_PROFILE_ENV)));
}
#[test]
fn build_cargo_command_nextest_profile_injects_profile_flag() {
let cmd = build_cargo_command(
TEST_SUB_ARGV,
false,
None,
Some("ci"),
false,
false,
&["-E".to_string(), "test(foo)".to_string()],
);
let argv: Vec<&std::ffi::OsStr> = cmd.get_args().collect();
assert_eq!(
argv,
["nextest", "run", "--profile", "ci", "-E", "test(foo)"].map(std::ffi::OsStr::new),
);
let cmd = build_cargo_command(TEST_SUB_ARGV, false, None, None, false, false, &[]);
let argv: Vec<&std::ffi::OsStr> = cmd.get_args().collect();
assert_eq!(argv, ["nextest", "run"].map(std::ffi::OsStr::new));
}
#[test]
fn kernel_set_or_bail_all_whitespace_bails() {
let err = kernel_set_or_bail(&["".to_string(), " \t ".to_string()], false)
.expect_err("all-whitespace --kernel must bail, not auto-discover");
assert!(
err.starts_with("--kernel: every supplied value parsed to empty"),
"unexpected bail message: {err}",
);
}
#[test]
fn kernel_set_or_bail_empty_input_is_ok_empty() {
assert_eq!(kernel_set_or_bail(&[], false), Ok(Vec::new()));
}
#[test]
fn generate_btf_anchor_missing_build_root_is_none() {
let dir = tempfile::tempdir().expect("tempdir");
assert_eq!(generate_btf_anchor(dir.path(), false), None);
assert_eq!(generate_btf_anchor(dir.path(), true), None);
}
#[test]
fn generate_btf_anchor_empty_build_root_is_none() {
let dir = tempfile::tempdir().expect("tempdir");
std::fs::create_dir_all(dir.path().join("debug").join("build"))
.expect("create debug/build");
assert_eq!(generate_btf_anchor(dir.path(), false), None);
}
#[test]
fn generate_btf_anchor_build_entry_without_bpf_object_is_none() {
let dir = tempfile::tempdir().expect("tempdir");
let out = dir
.path()
.join("debug")
.join("build")
.join("scx_utils-abc123")
.join("out");
std::fs::create_dir_all(&out).expect("create out");
std::fs::write(out.join("other.bpf.o"), b"x").expect("write other.bpf.o");
std::fs::write(out.join("bpf.o"), b"x").expect("write bpf.o");
assert_eq!(generate_btf_anchor(dir.path(), false), None);
}
}