pub(crate) mod toolchain;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, MutexGuard};
use std::time::Duration;
pub(crate) static CWD_LOCK: Mutex<()> = Mutex::new(());
pub(crate) struct CwdGuard {
_lock: MutexGuard<'static, ()>,
original: PathBuf,
}
impl CwdGuard {
pub(crate) fn enter(dir: &Path) -> Self {
let lock = CWD_LOCK.lock().unwrap_or_else(|error| error.into_inner());
let original = std::env::current_dir().expect("read current directory");
std::env::set_current_dir(dir).expect("enter directory");
Self { _lock: lock, original }
}
}
impl Drop for CwdGuard {
fn drop(&mut self) {
let _ = std::env::set_current_dir(&self.original);
}
}
pub(crate) static SKIP_COMMANDS_LOCK: Mutex<()> = Mutex::new(());
pub(crate) struct SkipCommandsGuard {
_lock: MutexGuard<'static, ()>,
previous: Option<String>,
}
impl SkipCommandsGuard {
pub(crate) fn set(value: &str) -> Self {
let lock = SKIP_COMMANDS_LOCK.lock().unwrap_or_else(|error| error.into_inner());
let previous = std::env::var("ALEF_SKIP_COMMANDS").ok();
unsafe { std::env::set_var("ALEF_SKIP_COMMANDS", value) };
Self { _lock: lock, previous }
}
}
impl Drop for SkipCommandsGuard {
fn drop(&mut self) {
unsafe {
match &self.previous {
Some(value) => std::env::set_var("ALEF_SKIP_COMMANDS", value),
None => std::env::remove_var("ALEF_SKIP_COMMANDS"),
}
}
}
}
pub(crate) static REAL_CARGO_LOCK: Mutex<()> = Mutex::new(());
pub(crate) struct RealCargoGuard {
_lock: MutexGuard<'static, ()>,
}
impl RealCargoGuard {
pub(crate) fn acquire() -> Self {
let lock = REAL_CARGO_LOCK.lock().unwrap_or_else(|error| error.into_inner());
Self { _lock: lock }
}
}
pub(crate) static REAL_MVN_LOCK: Mutex<()> = Mutex::new(());
pub(crate) struct RealMvnGuard {
_lock: MutexGuard<'static, ()>,
}
impl RealMvnGuard {
pub(crate) fn acquire() -> Self {
let lock = REAL_MVN_LOCK.lock().unwrap_or_else(|error| error.into_inner());
Self { _lock: lock }
}
}
pub(crate) fn maven_local_repo_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("mvn-repo-cache-test")
}
pub(crate) fn mvn_is_runnable() -> bool {
static RUNNABLE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*RUNNABLE.get_or_init(|| {
spawn_from_stable_dir("mvn")
.arg("--version")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
})
}
pub(crate) fn spawn_from_stable_dir(program: &str) -> std::process::Command {
let mut command = std::process::Command::new(program);
command.current_dir(std::env::temp_dir());
command
}
pub(crate) fn assert_elapsed_under(context: &str, elapsed: Duration, bound: Duration) {
let margin = bound.as_secs_f64() - elapsed.as_secs_f64();
assert!(
elapsed < bound,
"{context}: elapsed {elapsed:?}, bound {bound:?}, margin {margin:+.3}s -- a negative \
margin here next to an otherwise-clean run is the signature of CPU starvation from a \
concurrent process, not a defect in the code under test"
);
}
pub(crate) mod cargo_sort_order {
pub(crate) const DEF_TABLE_ORDER: &[&str] = &[
"package",
"workspace",
"lib",
"bin",
"features",
"dependencies",
"build-dependencies",
"dev-dependencies",
];
fn header_segments(inner: &str) -> Vec<String> {
let mut segments = Vec::new();
let mut current = String::new();
let mut quote: Option<char> = None;
for character in inner.chars() {
match quote {
Some(open) if character == open => quote = None,
Some(_) => current.push(character),
None if character == '\'' || character == '"' => quote = Some(character),
None if character == '.' => segments.push(std::mem::take(&mut current)),
None => current.push(character),
}
}
segments.push(current);
segments
}
fn rank_of(name: &str) -> usize {
DEF_TABLE_ORDER
.iter()
.position(|table| *table == name)
.unwrap_or(DEF_TABLE_ORDER.len())
}
fn table_sort_key(inner: &str) -> (usize, u8) {
let segments = header_segments(inner);
let first = segments.first().map(String::as_str).unwrap_or_default();
if first == "target" {
let kind = segments.last().map(String::as_str).unwrap_or_default();
return (rank_of(kind), 1);
}
(rank_of(first), 0)
}
const DEPENDENCY_TABLES: &[&str] = &["dependencies", "dev-dependencies", "build-dependencies"];
pub(crate) fn assert_dependency_keys_sorted(label: &str, manifest: &str) -> usize {
let document = manifest
.parse::<toml_edit::DocumentMut>()
.unwrap_or_else(|error| panic!("{label}: generated manifest must be valid TOML: {error}\n{manifest}"));
let mut compared = 0usize;
assert_table_keys_sorted(label, "", document.as_table(), manifest, &mut compared);
compared
}
fn assert_table_keys_sorted(
label: &str,
path: &str,
table: &toml_edit::Table,
manifest: &str,
compared: &mut usize,
) {
for (name, item) in table.iter() {
let Some(child) = item.as_table() else { continue };
if child.is_dotted() {
continue;
}
let child_path = if path.is_empty() {
name.to_owned()
} else {
format!("{path}.{name}")
};
if DEPENDENCY_TABLES.contains(&name) {
let emitted: Vec<&str> = child.iter().map(|(key, _)| key).collect();
let mut expected = emitted.clone();
expected.sort_unstable();
assert_eq!(
emitted, expected,
"{label}: `[{child_path}]` keys are not in cargo-sort order -- it compares the \
bare dependency NAME (a dotted key like `foo.workspace` is the single key \
`foo`), so `cargo sort --check` would reorder this manifest and fail it:\n{manifest}"
);
*compared += emitted.len();
}
assert_table_keys_sorted(label, &child_path, child, manifest, compared);
}
}
pub(crate) fn assert_canonical_table_order(label: &str, manifest: &str) {
let mut previous: Option<((usize, u8), &str)> = None;
let mut header_count = 0usize;
for line in manifest.lines() {
let trimmed = line.trim();
let Some(inner) = trimmed.strip_prefix('[').and_then(|rest| rest.strip_suffix(']')) else {
continue;
};
let inner = inner
.strip_prefix('[')
.and_then(|rest| rest.strip_suffix(']'))
.unwrap_or(inner);
header_count += 1;
let key = table_sort_key(inner);
if let Some((previous_key, previous_header)) = previous {
assert!(
key >= previous_key,
"{label}: table `{trimmed}` must not follow `{previous_header}` -- cargo-sort \
orders tables {DEF_TABLE_ORDER:?} first and every other table after them, so \
`cargo sort --check` would reorder this manifest and fail it:\n{manifest}"
);
}
previous = Some((key, trimmed));
}
assert!(
header_count > 0,
"{label}: no table headers found, so this check examined nothing:\n{manifest}"
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_reject_lints_table_placed_before_dependencies() {
let manifest = "[package]\nname = \"demo\"\n\n[lints.clippy]\ndbg_macro = \"deny\"\n\n\
[dependencies]\nserde = \"1\"\n";
let result = std::panic::catch_unwind(|| assert_canonical_table_order("demo", manifest));
assert!(result.is_err(), "checker must reject lints emitted before dependencies");
}
#[test]
fn should_accept_lints_table_placed_last() {
let manifest = "[package]\nname = \"demo\"\n\n[dependencies]\nserde = \"1\"\n\n\
[lints.clippy]\ndbg_macro = \"deny\"\n";
assert_canonical_table_order("demo", manifest);
}
#[test]
fn should_accept_subtables_and_target_dependency_blocks() {
let manifest = "[package]\nname = \"demo\"\n\n[package.metadata.cargo-machete]\n\
ignored = []\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[features]\n\
default = []\n\n[dependencies]\nserde = \"1\"\n\n\
[target.'cfg(unix)'.dependencies]\nlibc = \"0.2\"\n\n\
[build-dependencies]\ncc = \"1\"\n\n[dev-dependencies]\n\
tempfile = \"3\"\n\n[lints.clippy]\ndbg_macro = \"deny\"\n";
assert_canonical_table_order("demo", manifest);
}
#[test]
fn should_reject_dotted_key_ordered_by_raw_line_text() {
let manifest = "[package]\nname = \"demo\"\n\n[dependencies]\n\
alpha-parser = { version = \"1\", path = \"../core\" }\nalpha.workspace = true\n";
let result = std::panic::catch_unwind(|| assert_dependency_keys_sorted("demo", manifest));
assert!(
result.is_err(),
"checker must reject `alpha-parser` emitted before `alpha.workspace`"
);
}
#[test]
fn should_accept_dotted_key_ordered_by_dependency_name() {
let manifest = "[package]\nname = \"demo\"\n\n[dependencies]\n\
alpha.workspace = true\nalpha-parser = { version = \"1\", path = \"../core\" }\n";
assert_eq!(
assert_dependency_keys_sorted("demo", manifest),
2,
"both dependency keys must have been compared"
);
}
#[test]
fn should_check_target_and_workspace_dependency_tables() {
let manifest = "[workspace]\nmembers = []\n\n[workspace.dependencies]\n\
serde = \"1\"\n\n[target.'cfg(unix)'.dependencies]\nlibc = \"0.2\"\n";
assert_eq!(
assert_dependency_keys_sorted("demo", manifest),
2,
"the workspace and target dependency tables must both have been visited"
);
let broken = "[target.'cfg(unix)'.dependencies]\nlibc-extra = \"1\"\nlibc.workspace = true\n";
let result = std::panic::catch_unwind(|| assert_dependency_keys_sorted("demo", broken));
assert!(result.is_err(), "checker must reach into target dependency tables");
}
#[test]
fn should_reject_features_table_placed_after_dependencies() {
let manifest = "[package]\nname = \"demo\"\n\n[dependencies]\nserde = \"1\"\n\n\
[features]\ndefault = []\n";
let result = std::panic::catch_unwind(|| assert_canonical_table_order("demo", manifest));
assert!(
result.is_err(),
"checker must reject features emitted after dependencies"
);
}
}
}
pub(crate) fn git_command(root: &Path) -> std::process::Command {
let mut command = std::process::Command::new("git");
command
.current_dir(root)
.env("GIT_CONFIG_GLOBAL", "/dev/null")
.env("GIT_CONFIG_SYSTEM", "/dev/null")
.env("GIT_CONFIG_NOSYSTEM", "1")
.args([
"-c",
"user.name=Alef Test",
"-c",
"user.email=test@example.com",
"-c",
"commit.gpgsign=false",
"-c",
"tag.gpgsign=false",
"-c",
"core.excludesFile=/dev/null",
"-c",
"init.defaultBranch=main",
]);
command
}
pub(crate) fn git_init(root: &Path) {
let status = git_command(root).args(["init", "-q"]).status().expect("git init");
assert!(status.success(), "git init must succeed for a tracked-ness fixture");
}
pub(crate) fn git_add(root: &Path, relative: &[&str]) {
let status = git_command(root)
.arg("add")
.arg("--")
.args(relative)
.status()
.expect("git add");
assert!(status.success(), "git add must succeed for a tracked-ness fixture");
}
pub(crate) fn git_commit_all(root: &Path, message: &str) {
let status = git_command(root).args(["add", "-A"]).status().expect("git add -A");
assert!(status.success(), "git add -A must succeed for a history fixture");
let status = git_command(root)
.args(["commit", "--quiet", "-m", message])
.status()
.expect("git commit");
assert!(status.success(), "git commit must succeed for a history fixture");
}
pub(crate) fn portable_path_string(path: &Path) -> String {
path.to_string_lossy().replace('\\', "/")
}
pub(crate) fn write_file(root: &Path, relative: &str, content: &str) -> PathBuf {
let path = root.join(relative);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("create parent directory");
}
std::fs::write(&path, content).expect("write fixture file");
path
}
#[cfg(test)]
mod git_hermeticity_tests {
use super::{git_command, git_commit_all, git_init, write_file};
#[test]
fn git_commit_all_is_unsigned_even_when_local_config_demands_signing() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
git_init(root);
let configured = git_command(root)
.args(["config", "--local", "commit.gpgsign", "true"])
.status()
.expect("git config");
assert!(configured.success(), "the hostile local config must be written");
let configured = git_command(root)
.args(["config", "--local", "user.signingkey", "DEADBEEFDEADBEEF"])
.status()
.expect("git config");
assert!(configured.success(), "the bogus signing key must be written");
write_file(root, "build.zig", "const std = @import(\"std\");\n");
git_commit_all(root, "scaffold");
let signature = git_command(root)
.args(["log", "-1", "--format=%G?"])
.output()
.expect("git log");
assert!(signature.status.success(), "git log must succeed");
assert_eq!(
String::from_utf8_lossy(&signature.stdout).trim(),
"N",
"the fixture commit must carry no signature, so the test never depends on a working \
gpg-agent on the machine running it"
);
}
#[test]
fn git_init_uses_a_pinned_default_branch_name() {
let dir = tempfile::tempdir().expect("tempdir");
let root = dir.path();
git_init(root);
write_file(root, "seed.txt", "seed\n");
git_commit_all(root, "scaffold");
let branch = git_command(root)
.args(["rev-parse", "--abbrev-ref", "HEAD"])
.output()
.expect("git rev-parse");
assert_eq!(
String::from_utf8_lossy(&branch.stdout).trim(),
"main",
"the fixture branch name must be pinned by the helper, not inherited from the host"
);
}
}
#[cfg(test)]
mod assert_elapsed_under_tests {
use super::assert_elapsed_under;
use std::time::Duration;
#[test]
fn does_not_panic_when_elapsed_is_under_the_bound() {
assert_elapsed_under("probe", Duration::from_millis(100), Duration::from_secs(1));
}
#[test]
fn panics_with_elapsed_bound_and_margin_when_over_the_bound() {
let result = std::panic::catch_unwind(|| {
assert_elapsed_under("probe", Duration::from_millis(1200), Duration::from_secs(1));
});
let error = result.expect_err("elapsed over the bound must panic");
let message = *error.downcast::<String>().expect("panic payload is a String");
assert!(message.contains("probe"), "message must name the context: {message}");
assert!(
message.contains("1.2s"),
"message must state the measured elapsed: {message}"
);
assert!(
message.contains("1s"),
"message must state the asserted bound: {message}"
);
assert!(
message.contains("margin -0.200s"),
"message must state the signed margin between elapsed and bound: {message}"
);
}
}
#[cfg(test)]
mod portable_path_string_tests {
use super::portable_path_string;
use std::path::Path;
#[test]
fn replaces_every_backslash_with_a_forward_slash() {
assert_eq!(
portable_path_string(Path::new("packages\\java\\src")),
"packages/java/src"
);
}
#[test]
fn leaves_a_forward_slash_path_unchanged() {
assert_eq!(
portable_path_string(Path::new("packages/java/src")),
"packages/java/src"
);
}
#[test]
fn normalizes_a_path_mixing_both_separators() {
assert_eq!(
portable_path_string(Path::new("root\\target/release/deps\\lib.so")),
"root/target/release/deps/lib.so"
);
}
#[test]
fn does_not_equate_genuinely_different_paths() {
assert_ne!(
portable_path_string(Path::new("packages\\java")),
portable_path_string(Path::new("packages\\kotlin"))
);
}
}