use fancy_regex::Regex; use nu_json::Value; use std::fs; use std::io; use std::path::{Path, PathBuf}; fn txt(text: &str) -> String { let out = String::from_utf8_lossy(text.as_bytes()); #[cfg(windows)] { out.replace("\r\n", "").replace('\n', "") } #[cfg(not(windows))] { out.to_string() } } fn hjson_expectations() -> PathBuf { nu_test_support::fs::assets().join("nu_json") } fn get_test_content(name: &str) -> io::Result { let expectations = hjson_expectations(); let mut p = format!("{}/{}_test.hjson", expectations.display(), name); if !Path::new(&p).exists() { p = format!("{}/{}_test.json", expectations.display(), name); } fs::read_to_string(&p) } fn get_result_content(name: &str) -> io::Result<(String, String)> { let expectations = hjson_expectations(); let p1 = format!("{}/{}_result.json", expectations.display(), name); let p2 = format!("{}/{}_result.hjson", expectations.display(), name); Ok((fs::read_to_string(p1)?, fs::read_to_string(p2)?)) } macro_rules! run_test { // {{ is a workaround for rust stable ($v: ident, $list: expr, $fix: expr) => {{ let name = stringify!($v); $list.push(format!("{}_test", name)); println!("- running {}", name); let should_fail = name.starts_with("fail"); let test_content = get_test_content(name).unwrap(); let data: nu_json::Result = nu_json::from_str(&test_content); assert!(should_fail == data.is_err()); if !should_fail { let udata = data.unwrap(); let (rjson, rhjson) = get_result_content(name).unwrap(); let rjson = txt(&rjson); let rhjson = txt(&rhjson); let actual_hjson = nu_json::to_string(&udata).unwrap(); let actual_hjson = txt(&actual_hjson); let actual_json = $fix(serde_json::to_string_pretty(&udata).unwrap()); let actual_json = txt(&actual_json); // nu_json::to_string now outputs json instead of hjson! if rjson != actual_hjson { println!( "{:?}\n---hjson expected\n{}\n---hjson actual\n{}\n---\n", name, rhjson, actual_hjson ); } if rjson != actual_json { println!( "{:?}\n---json expected\n{}\n---json actual\n{}\n---\n", name, rjson, actual_json ); } assert!(rjson == actual_hjson && rjson == actual_json); } }}; } // add fixes where rust's json differs from javascript fn std_fix(json: String) -> String { // serde_json serializes integers with a superfluous .0 suffix let re = Regex::new(r"(?m)(?P\d)\.0(?P,?)$").unwrap(); re.replace_all(&json, "$d$s").to_string() } fn fix_kan(json: String) -> String { std_fix(json).replace(" -0,", " 0,") } fn fix_pass1(json: String) -> String { std_fix(json) .replace("1.23456789e34", "1.23456789e+34") .replace("2.3456789012e76", "2.3456789012e+76") } #[test] fn test_hjson() { let mut done: Vec = Vec::new(); println!(); run_test!(charset, done, std_fix); run_test!(comments, done, std_fix); run_test!(empty, done, std_fix); run_test!(failCharset1, done, std_fix); run_test!(failJSON02, done, std_fix); run_test!(failJSON05, done, std_fix); run_test!(failJSON06, done, std_fix); run_test!(failJSON07, done, std_fix); run_test!(failJSON08, done, std_fix); run_test!(failJSON10, done, std_fix); run_test!(failJSON11, done, std_fix); run_test!(failJSON12, done, std_fix); run_test!(failJSON13, done, std_fix); run_test!(failJSON14, done, std_fix); run_test!(failJSON15, done, std_fix); run_test!(failJSON16, done, std_fix); run_test!(failJSON17, done, std_fix); run_test!(failJSON19, done, std_fix); run_test!(failJSON20, done, std_fix); run_test!(failJSON21, done, std_fix); run_test!(failJSON22, done, std_fix); run_test!(failJSON23, done, std_fix); run_test!(failJSON24, done, std_fix); run_test!(failJSON26, done, std_fix); run_test!(failJSON28, done, std_fix); run_test!(failJSON29, done, std_fix); run_test!(failJSON30, done, std_fix); run_test!(failJSON31, done, std_fix); run_test!(failJSON32, done, std_fix); run_test!(failJSON33, done, std_fix); run_test!(failJSON34, done, std_fix); run_test!(failKey1, done, std_fix); run_test!(failKey2, done, std_fix); run_test!(failKey3, done, std_fix); run_test!(failKey4, done, std_fix); run_test!(failMLStr1, done, std_fix); run_test!(failObj1, done, std_fix); run_test!(failObj2, done, std_fix); run_test!(failObj3, done, std_fix); run_test!(failStr1a, done, std_fix); run_test!(failStr1b, done, std_fix); run_test!(failStr1c, done, std_fix); run_test!(failStr1d, done, std_fix); run_test!(failStr2a, done, std_fix); run_test!(failStr2b, done, std_fix); run_test!(failStr2c, done, std_fix); run_test!(failStr2d, done, std_fix); run_test!(failStr3a, done, std_fix); run_test!(failStr3b, done, std_fix); run_test!(failStr3c, done, std_fix); run_test!(failStr3d, done, std_fix); run_test!(failStr4a, done, std_fix); run_test!(failStr4b, done, std_fix); run_test!(failStr4c, done, std_fix); run_test!(failStr4d, done, std_fix); run_test!(failStr5a, done, std_fix); run_test!(failStr5b, done, std_fix); run_test!(failStr5c, done, std_fix); run_test!(failStr5d, done, std_fix); run_test!(failStr6a, done, std_fix); run_test!(failStr6b, done, std_fix); run_test!(failStr6c, done, std_fix); run_test!(failStr6d, done, std_fix); run_test!(kan, done, fix_kan); run_test!(keys, done, std_fix); run_test!(oa, done, std_fix); run_test!(pass1, done, fix_pass1); run_test!(pass2, done, std_fix); run_test!(pass3, done, std_fix); run_test!(pass4, done, std_fix); run_test!(passSingle, done, std_fix); run_test!(root, done, std_fix); run_test!(stringify1, done, std_fix); run_test!(strings, done, std_fix); run_test!(trail, done, std_fix); // check if we include all assets let paths = fs::read_dir(hjson_expectations()).unwrap(); let all = paths .map(|item| String::from(item.unwrap().path().file_stem().unwrap().to_str().unwrap())) .filter(|x| x.contains("_test")); let missing = all .into_iter() .filter(|x| !done.iter().any(|y| x == y)) .collect::>(); if !missing.is_empty() { for item in missing { println!("missing: {}", item); } panic!(); } }