cargo-allow 0.1.11

Source-tree exception ledger and policy scanner for Rust repositories.
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Child, Command};

#[test]
fn concurrent_adds_reload_policy_under_the_mutation_lock() -> Result<(), Box<dyn std::error::Error>>
{
    let root = TempRoot::new("concurrent-add")?;
    fs::create_dir_all(root.path().join("src"))?;
    fs::create_dir_all(root.path().join("policy"))?;
    fs::write(
        root.path().join("src/a.rs"),
        "pub fn a() -> u32 { let value: Option<u32> = None; value.unwrap() }\n",
    )?;
    fs::write(
        root.path().join("src/b.rs"),
        "pub fn b() -> u32 { let value: Option<u32> = None; value.unwrap() }\n",
    )?;
    fs::write(root.path().join("policy/allow.toml"), base_policy())?;
    let git_init = Command::new("git")
        .arg("init")
        .arg("--quiet")
        .current_dir(root.path())
        .status()?;
    if !git_init.success() {
        return Err("git init failed".into());
    }
    let git_add = Command::new("git")
        .args(["add", "src", "policy/allow.toml"])
        .current_dir(root.path())
        .status()?;
    if !git_add.success() {
        return Err("git add failed".into());
    }

    let first = add_command(&root, "src/a.rs").spawn()?;
    let second = add_command(&root, "src/b.rs").spawn()?;
    wait_success(first, "first concurrent add")?;
    wait_success(second, "second concurrent add")?;

    let policy = fs::read_to_string(root.path().join("policy/allow.toml"))?;
    if !policy.contains("glob = \"src/a.rs\"") || !policy.contains("glob = \"src/b.rs\"") {
        return Err("concurrent adds did not preserve both policy mutations".into());
    }
    Ok(())
}

fn add_command(root: &TempRoot, glob: &str) -> Command {
    let mut command = Command::new(env!("CARGO_BIN_EXE_cargo-allow"));
    command
        .arg("add")
        .arg("--root")
        .arg(root.path())
        .arg("--config")
        .arg("policy/allow.toml")
        .arg("--kind")
        .arg("panic")
        .arg("--family")
        .arg("unwrap")
        .arg("--callee")
        .arg("unwrap")
        .arg("--glob")
        .arg(glob)
        .arg("--owner")
        .arg("concurrency-test")
        .arg("--reason")
        .arg("concurrent mutation regression")
        .arg("--write")
        .arg(root.path().join("policy/allow.toml"))
        .arg("--force");
    command
}

fn wait_success(child: Child, label: &str) -> Result<(), Box<dyn std::error::Error>> {
    let output = child.wait_with_output()?;
    if !output.status.success() {
        return Err(format!(
            "{label} failed with status {:?}: stdout=`{}` stderr=`{}`",
            output.status.code(),
            String::from_utf8_lossy(&output.stdout),
            String::from_utf8_lossy(&output.stderr)
        )
        .into());
    }
    Ok(())
}

fn base_policy() -> &'static str {
    r#"schema_version = "0.1"
policy = "cargo-allow"
owner = "core"
status = "active"

[workspace]
root = "."
inventory = "git-tracked"
default_mode = "no-new"
ignored = ["policy/**"]
generated = ["target/**"]

[requirements]
owner_required = true
reason_required = true
classification_required = true
evidence_required = false
expires_or_review_after_required = true
allow_bare_allow_attributes = false
lint_policy_id_required = false
stale_entries_fail = false

[[allow]]
id = "allow-policy"
kind = "non_rust_file"
family = "configuration"
glob = "policy/*.toml"
owner = "core"
classification = "fixture"
reason = "policy files"
created = "2026-01-01"
review_after = "2026-12-01"

[allow.selector]
ast_kind = "tracked_file"
target_fingerprint = "toml"
glob = "policy/*.toml"
"#
}

struct TempRoot {
    path: PathBuf,
}

impl TempRoot {
    fn new(label: &str) -> Result<Self, Box<dyn std::error::Error>> {
        let unique = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_nanos();
        let path = std::env::temp_dir().join(format!(
            "cargo-allow-{label}-{}-{unique}",
            std::process::id()
        ));
        fs::create_dir_all(&path)?;
        Ok(Self { path })
    }

    fn path(&self) -> &Path {
        &self.path
    }
}

impl Drop for TempRoot {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.path);
    }
}