use super::*;
use std::path::PathBuf;
fn variant_index(e: &Error) -> usize {
match e {
Error::NotARepository(_) => 0,
Error::OpenRepository(_) => 1,
Error::ResolveRef { .. } => 2,
Error::Walk(_) => 3,
Error::Diff(_) => 4,
Error::Mailmap(_) => 5,
Error::InvalidBotPattern(_) => 6,
Error::InvalidWindow(_) => 7,
Error::InvalidTimestamp(_) => 8,
Error::InvalidFormula(_) => 9,
Error::InvalidFileTypeScope(_) => 10,
Error::InvalidBusFactorThreshold(_) => 11,
Error::InvalidAuthorHashKey(_) => 12,
Error::InvalidTrend(_) => 13,
Error::Blame(_) => 14,
Error::InvalidDiff(_) => 15,
Error::Cache(_) => 16,
}
}
const VARIANT_COUNT: usize = 17;
fn assert_covers_every_variant<'a>(errors: impl IntoIterator<Item = &'a Error>, what: &str) {
let mut seen = [false; VARIANT_COUNT];
for err in errors {
let i = variant_index(err);
assert!(
!seen[i],
"{what}: two entries name the same variant ({err:?}); each must \
stand for exactly one, or a later variant hides behind it",
);
seen[i] = true;
}
let missing: Vec<usize> = seen
.iter()
.enumerate()
.filter_map(|(i, hit)| (!hit).then_some(i))
.collect();
assert!(
missing.is_empty(),
"{what}: {} of {VARIANT_COUNT} variants covered; missing the arms of \
`variant_index` at {missing:?}",
VARIANT_COUNT - missing.len(),
);
}
#[test]
fn display_covers_every_variant() {
let cases: Vec<(Error, &str)> = vec![
(
Error::NotARepository(PathBuf::from("/tmp/x")),
"not inside a supported version-control working tree",
),
(
Error::OpenRepository("corrupt".to_owned()),
"failed to open repository: corrupt",
),
(
Error::ResolveRef {
reference: "HEAD".to_owned(),
reason: "unborn".to_owned(),
},
"failed to resolve revision",
),
(
Error::Walk("boom".to_owned()),
"failed to walk commit history: boom",
),
(Error::Diff("bad".to_owned()), "failed to compute diff: bad"),
(
Error::Mailmap("nope".to_owned()),
"failed to apply .mailmap: nope",
),
(
Error::InvalidBotPattern("(".to_owned()),
"invalid bot pattern: (",
),
(
Error::InvalidWindow("empty".to_owned()),
"invalid time window: empty",
),
(
Error::InvalidTimestamp("xyz".to_owned()),
"invalid timestamp: xyz",
),
(
Error::InvalidFormula("bogus".to_owned()),
"unknown risk formula",
),
(
Error::InvalidFileTypeScope("empty".to_owned()),
"invalid file-type scope: empty",
),
(
Error::InvalidBusFactorThreshold("1.5".to_owned()),
"invalid bus-factor threshold: 1.5",
),
(
Error::InvalidAuthorHashKey("the key is empty".to_owned()),
"invalid author-hash key: the key is empty",
),
(
Error::InvalidTrend("one point".to_owned()),
"invalid trend parameters: one point",
),
(
Error::Blame("no such file".to_owned()),
"failed to blame file: no such file",
),
(
Error::InvalidDiff("bad hunk".to_owned()),
"invalid unified diff: bad hunk",
),
(
Error::Cache("disk full".to_owned()),
"history cache error: disk full",
),
];
assert_covers_every_variant(cases.iter().map(|(err, _)| err), "Display cases");
for (err, expected) in cases {
let rendered = err.to_string();
assert!(
rendered.contains(expected),
"Display for {err:?} = {rendered:?}, expected to contain {expected:?}"
);
}
}
#[test]
fn not_a_repository_names_the_offending_path() {
let err = Error::NotARepository(PathBuf::from("/tmp/not-a-repo"));
assert!(err.to_string().contains("/tmp/not-a-repo"));
}
#[test]
fn resolve_ref_names_reference_and_reason() {
let err = Error::ResolveRef {
reference: "feature/x".to_owned(),
reason: "no such ref".to_owned(),
};
let rendered = err.to_string();
assert!(rendered.contains("feature/x"), "{rendered:?}");
assert!(rendered.contains("no such ref"), "{rendered:?}");
}
#[test]
fn invalid_formula_lists_the_accepted_names() {
let rendered = Error::InvalidFormula("bogus".to_owned()).to_string();
assert!(rendered.contains("weighted"), "{rendered:?}");
assert!(rendered.contains("percentile"), "{rendered:?}");
}
const CLIENT_INPUT_COUNT: usize = 11;
#[test]
fn is_client_input_classifies_every_variant() {
let s = || "x".to_owned();
let client_input = Error::client_input_samples();
let environment: Vec<Error> = vec![
Error::OpenRepository(s()),
Error::Walk(s()),
Error::Diff(s()),
Error::Mailmap(s()),
Error::Blame(s()),
Error::Cache(s()),
];
assert_eq!(
client_input.len(),
CLIENT_INPUT_COUNT,
"the client-input group changed size; if that is deliberate, every \
new variant also needs an `error_kind` token in the web crate",
);
assert_covers_every_variant(
client_input.iter().chain(environment.iter()),
"classification",
);
for err in client_input {
assert!(err.is_client_input(), "{err:?} should be client input");
}
for err in environment {
assert!(!err.is_client_input(), "{err:?} should not be client input");
}
}