use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use avatarr_parser::Language;
#[derive(Deserialize)]
struct Fixture {
cases: Vec<Case>,
}
#[derive(Deserialize)]
struct Case {
input: String,
}
#[derive(Deserialize)]
struct Expectations {
mappings: HashMap<String, ExpectedEntry>,
}
#[derive(Deserialize)]
struct ExpectedEntry {
kind: String,
languages: Vec<String>,
}
enum Outcome {
Pass,
Fail,
Undecidable,
}
#[test]
fn language_fixtures_pass_rate_recorded() {
let fixture_path =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/sonarr-fixtures/languages.json");
let raw = std::fs::read_to_string(&fixture_path)
.unwrap_or_else(|e| panic!("read fixture {}: {e}", fixture_path.display()));
let fx: Fixture = serde_json::from_str(&raw).expect("parse languages.json");
let expectations: Expectations = serde_json::from_str(include_str!(
"../../../tests/sonarr-fixtures/languages_expectations.json"
))
.expect("parse languages_expectations.json");
let mut pass = 0u32;
let mut fail = 0u32;
let mut undecidable = 0u32;
let total = fx.cases.len() as u32;
let debug_failures = std::env::var("AVATARR_DEBUG_LANGUAGE_FAILURES").is_ok();
for case in &fx.cases {
let actual = avatarr_parser::parse_languages(&case.input);
match compare_against_expected(&case.input, &actual, &expectations) {
Outcome::Pass => pass += 1,
Outcome::Undecidable => undecidable += 1,
Outcome::Fail => {
fail += 1;
if debug_failures {
let expected = expectations.mappings.get(&case.input);
eprintln!(
" FAIL: input={:?} expected={:?} got={:?}",
case.input,
expected.map(|e| (e.kind.as_str(), &e.languages)),
actual,
);
}
}
}
}
let denominator = total - undecidable;
let pct = if denominator == 0 {
0.0
} else {
100.0 * pass as f64 / denominator as f64
};
eprintln!(
"language_fixtures: {pass}/{denominator} pass ({pct:.1}%); \
{fail} fail, {undecidable} undecidable, {total} total cases",
);
assert_eq!(
pass + fail + undecidable,
total,
"outcome counts must sum to total cases"
);
}
fn compare_against_expected(
input: &str,
actual: &[Language],
expectations: &Expectations,
) -> Outcome {
let Some(entry) = expectations.mappings.get(input) else {
return Outcome::Undecidable;
};
let expected: Vec<Language> = match entry
.languages
.iter()
.map(|s| token_to_language(s))
.collect::<Option<Vec<_>>>()
{
Some(v) => v,
None => return Outcome::Undecidable,
};
let actual_set: std::collections::HashSet<i32> = actual.iter().map(|l| *l as i32).collect();
let expected_set: std::collections::HashSet<i32> = expected.iter().map(|l| *l as i32).collect();
let pass = match entry.kind.as_str() {
"contains" => expected_set.is_subset(&actual_set),
"equivalent" => actual_set == expected_set,
_ => return Outcome::Undecidable,
};
if pass { Outcome::Pass } else { Outcome::Fail }
}
fn token_to_language(token: &str) -> Option<Language> {
Some(match token {
"Unknown" => Language::Unknown,
"English" => Language::English,
"French" => Language::French,
"Spanish" => Language::Spanish,
"German" => Language::German,
"Italian" => Language::Italian,
"Danish" => Language::Danish,
"Dutch" => Language::Dutch,
"Japanese" => Language::Japanese,
"Icelandic" => Language::Icelandic,
"Chinese" => Language::Chinese,
"Russian" => Language::Russian,
"Polish" => Language::Polish,
"Vietnamese" => Language::Vietnamese,
"Swedish" => Language::Swedish,
"Norwegian" => Language::Norwegian,
"Finnish" => Language::Finnish,
"Turkish" => Language::Turkish,
"Portuguese" => Language::Portuguese,
"Flemish" => Language::Flemish,
"Greek" => Language::Greek,
"Korean" => Language::Korean,
"Hungarian" => Language::Hungarian,
"Hebrew" => Language::Hebrew,
"Lithuanian" => Language::Lithuanian,
"Czech" => Language::Czech,
"Arabic" => Language::Arabic,
"Hindi" => Language::Hindi,
"Bulgarian" => Language::Bulgarian,
"Malayalam" => Language::Malayalam,
"Ukrainian" => Language::Ukrainian,
"Slovak" => Language::Slovak,
"Thai" => Language::Thai,
"PortugueseBrazil" => Language::PortugueseBrazil,
"SpanishLatino" => Language::SpanishLatino,
"Romanian" => Language::Romanian,
"Latvian" => Language::Latvian,
"Persian" => Language::Persian,
"Catalan" => Language::Catalan,
"Croatian" => Language::Croatian,
"Serbian" => Language::Serbian,
"Bosnian" => Language::Bosnian,
"Estonian" => Language::Estonian,
"Tamil" => Language::Tamil,
"Indonesian" => Language::Indonesian,
"Macedonian" => Language::Macedonian,
"Slovenian" => Language::Slovenian,
"Original" => Language::Original,
_ => return None,
})
}