use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use serde_json::Value;
pub fn cargo_allow_command() -> Command {
Command::new(env!("CARGO_BIN_EXE_cargo-allow"))
}
pub fn assert_status(command: &str, result: &Output, should_succeed: bool) {
assert_eq!(
result.status.success(),
should_succeed,
"{command} status mismatch: stdout=`{}` stderr=`{}`",
String::from_utf8_lossy(&result.stdout),
String::from_utf8_lossy(&result.stderr)
);
}
pub fn assert_stdout_empty(command: &str, result: &Output, message: &str) {
assert!(
result.stdout.is_empty(),
"{command} {message}: `{}`",
String::from_utf8_lossy(&result.stdout)
);
}
pub fn assert_stderr_empty(command: &str, result: &Output, message: &str) {
assert!(
result.stderr.is_empty(),
"{command} {message}: `{}`",
String::from_utf8_lossy(&result.stderr)
);
}
pub fn assert_saved_json_artifact(
path: &std::path::Path,
name: &str,
expected_schema_id: &str,
expected_command: &str,
) -> Value {
let contents = fs::read_to_string(path)
.unwrap_or_else(|err| std::panic::panic_any(format!("read {}: {err}", path.display())));
let value: Value = serde_json::from_str(&contents).unwrap_or_else(|err| {
std::panic::panic_any(format!(
"{name} saved artifact should parse as JSON: {err}\n{contents}"
))
});
assert_eq!(
value.get("schema_version").and_then(Value::as_u64),
Some(1),
"{name} schema_version"
);
assert_eq!(
value.get("schema_id").and_then(Value::as_str),
Some(expected_schema_id),
"{name} schema_id"
);
assert_eq!(
value.get("tool").and_then(Value::as_str),
Some("cargo-allow"),
"{name} tool"
);
assert_eq!(
value.get("command").and_then(Value::as_str),
Some(expected_command),
"{name} command"
);
assert_json_string_array_eq(&value, "claim_boundary", allow_report::CLAIM_BOUNDARY, name);
assert_json_string_array_eq(
&value,
"scanner_limitations",
allow_report::SCANNER_LIMITATIONS,
name,
);
assert_eq!(
value.pointer("/inventory/scope").and_then(Value::as_str),
Some("source_tree"),
"{name} inventory scope"
);
assert_eq!(
value.pointer("/inventory/scanner").and_then(Value::as_str),
Some(expected_inventory_scanner(name, expected_schema_id)),
"{name} inventory scanner"
);
assert!(
value
.pointer("/inventory/source")
.and_then(Value::as_str)
.is_some_and(|source| !source.is_empty()),
"{name} inventory source"
);
value
}
fn expected_inventory_scanner(name: &str, expected_schema_id: &str) -> &'static str {
allow_report::artifact_contract_for_schema_id(expected_schema_id)
.map(|contract| contract.inventory_scanner)
.unwrap_or_else(|| {
std::panic::panic_any(format!(
"{name} expected schema_id {expected_schema_id} has no registered inventory scanner"
))
})
}
fn assert_json_string_array_eq(value: &Value, field: &str, expected: &[&str], artifact: &str) {
let Some(items) = value.get(field).and_then(Value::as_array) else {
std::panic::panic_any(format!("{artifact} {field} should be an array"));
};
let actual = items
.iter()
.map(|item| {
item.as_str().unwrap_or_else(|| {
std::panic::panic_any(format!("{artifact} {field} entries should be strings"))
})
})
.collect::<Vec<_>>();
assert_eq!(actual, expected, "{artifact} {field}");
}
pub fn temp_root(label: &str) -> PathBuf {
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_else(|err| std::panic::panic_any(format!("system clock: {err}")))
.as_nanos();
let root = std::env::temp_dir().join(format!(
"cargo-allow-{label}-{}-{unique}",
std::process::id()
));
fs::create_dir_all(&root)
.unwrap_or_else(|err| std::panic::panic_any(format!("create temp root: {err}")));
root
}
pub fn remove_temp_root(root: PathBuf) {
match fs::remove_dir_all(&root) {
Ok(()) => {}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => std::panic::panic_any(format!("remove temp root {}: {err}", root.display())),
}
}