use { crate::{ toml_parser::{ErrorHandler, ParserError, parse}, toml_span::{Span, Spanned, SpannedExt}, toml_value::Value, }, bstr::{BStr, ByteSlice}, std::{ os::unix::ffi::OsStrExt, panic::{AssertUnwindSafe, catch_unwind}, str::FromStr, }, walkdir::WalkDir, }; #[test] fn test() { let mut have_failures = false; let mut num = 0; let tests = std::path::Path::new(concat!( env!("CARGO_MANIFEST_DIR"), "/../toml-config/toml-test/tests/valid" )); if !tests.exists() { eprintln!("skipping TOML conformance tests because fixtures are not present"); return; } for path in WalkDir::new(tests) { let path = path.unwrap(); if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") { num += 1; let res = catch_unwind(AssertUnwindSafe(|| { have_failures |= run_test(prefix.as_bstr()); })); if res.is_err() { eprintln!("panic while running {}", prefix.as_bstr()); } } } if have_failures { panic!("There were test failures"); } eprintln!("ran {num} tests"); } fn run_test(prefix: &BStr) -> bool { let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap(); let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap(); let json: serde_json::Value = serde_json::from_str(&json).unwrap(); let json_as_toml = json_to_value(json); let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix)) { Ok(t) => t, Err(e) => { eprintln!("toml could not be parsed in test {}", prefix); NoErrorHandler(prefix).handle(e); return true; } }; if toml != json_as_toml { eprintln!("toml and json differ in test {}", prefix); eprintln!("toml: {:#?}", toml); eprintln!("json: {:#?}", json_as_toml); true } else { false } } fn json_to_value(json: serde_json::Value) -> Spanned { let span = Span { lo: 0, hi: 0 }; let val = match json { serde_json::Value::String(_) | serde_json::Value::Number(_) | serde_json::Value::Null | serde_json::Value::Bool(_) => panic!("Unexpected type"), serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()), serde_json::Value::Object(v) => { if v.len() == 2 && v.contains_key("type") && v.contains_key("value") { let ty = v.get("type").unwrap().as_str().unwrap(); let val = v.get("value").unwrap().as_str().unwrap(); match ty { "string" => Value::String(val.to_owned()), "integer" => Value::Integer(i64::from_str(val).unwrap()), "float" => Value::Float(f64::from_str(val).unwrap()), "bool" => Value::Boolean(bool::from_str(val).unwrap()), _ => panic!("unexpected type {}", ty), } } else { Value::Table( v.into_iter() .map(|(k, v)| (k.spanned(span), json_to_value(v))) .collect(), ) } } }; val.spanned(span) } struct NoErrorHandler<'a>(&'a BStr); impl<'a> ErrorHandler for NoErrorHandler<'a> { fn handle(&self, err: Spanned) { eprintln!( "{}: An error occurred during validation: {}", self.0, FormatError(err.span, Some(err.value)), ); } fn redefinition(&self, err: Spanned, prev: Span) { eprintln!( "{}: Redefinition: {}", self.0, FormatError(err.span, Some(err.value)), ); eprintln!( "{}: Previous: {}", self.0, FormatError(prev, None), ); } } struct FormatError(Span, Option); impl std::fmt::Display for FormatError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(cause) = &self.1 { write!(f, "{cause}: ")?; } write!(f, "span {}..{}", self.0.lo, self.0.hi) } }