use crate::merge::MissingCoveragePolicy;
use anyhow::{Context, Result};
use serde::Deserialize;
use std::fs;
use std::path::Path;
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Config {
pub threshold: Option<f64>,
pub fail_above: Option<bool>,
pub missing: Option<MissingCoveragePolicy>,
#[serde(default)]
pub exclude: Vec<String>,
pub top: Option<usize>,
pub min: Option<f64>,
#[serde(default)]
pub allow: Vec<String>,
pub fail_regression: Option<bool>,
pub jobs: Option<usize>,
pub epsilon: Option<f64>,
}
pub fn load(start: &Path) -> Result<Config> {
let mut dir = if start.is_file() {
start.parent().unwrap_or(start)
} else {
start
};
loop {
let candidate = dir.join(".cargo-crap.toml");
if candidate.exists() {
let raw = fs::read_to_string(&candidate)
.with_context(|| format!("reading {}", candidate.display()))?;
let cfg: Config =
toml::from_str(&raw).with_context(|| format!("parsing {}", candidate.display()))?;
return Ok(cfg);
}
match dir.parent() {
Some(p) => dir = p,
None => return Ok(Config::default()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
fn write_config(
dir: &Path,
content: &str,
) {
let mut f = fs::File::create(dir.join(".cargo-crap.toml")).unwrap();
f.write_all(content.as_bytes()).unwrap();
}
#[test]
fn missing_config_returns_defaults() {
let dir = tempfile::tempdir().unwrap();
let cfg = load(dir.path()).unwrap();
assert!(cfg.threshold.is_none());
assert!(cfg.fail_above.is_none());
assert!(cfg.missing.is_none());
assert!(cfg.exclude.is_empty());
assert!(cfg.allow.is_empty());
}
#[test]
fn config_file_is_parsed() {
let dir = tempfile::tempdir().unwrap();
write_config(
dir.path(),
r#"
threshold = 20.0
fail-above = true
missing = "optimistic"
exclude = ["tests/**"]
allow = ["Foo::*"]
"#,
);
let cfg = load(dir.path()).unwrap();
assert_eq!(cfg.threshold, Some(20.0));
assert_eq!(cfg.fail_above, Some(true));
assert_eq!(cfg.missing, Some(MissingCoveragePolicy::Optimistic));
assert_eq!(cfg.exclude, ["tests/**"]);
assert_eq!(cfg.allow, ["Foo::*"]);
}
#[test]
fn config_is_found_by_walking_up() {
let dir = tempfile::tempdir().unwrap();
write_config(dir.path(), "threshold = 15.0\n");
let subdir = dir.path().join("src");
fs::create_dir(&subdir).unwrap();
let cfg = load(&subdir).unwrap();
assert_eq!(cfg.threshold, Some(15.0));
}
#[test]
fn unknown_key_returns_error() {
let dir = tempfile::tempdir().unwrap();
write_config(dir.path(), "unknown-key = true\n");
let err = load(dir.path()).unwrap_err();
assert!(
err.to_string().contains("parsing"),
"expected parse error, got: {err}"
);
}
}