1use std::{2 env, fs,3 io::{self, ErrorKind},4 path::{Path, PathBuf},5};67use jrsonnet_evaluator::{8 FileImportResolver, IStr, ObjValueBuilder, State, Val, apply_tla,9 gc::WithCapacityExt as _,10 manifest::JsonFormat,11 rustc_hash::FxHashMap,12 stack::limit_stack_depth,13 tla::TlaArg,14 trace::{CompactFormat, PathResolver, TraceFormat},15};16use jrsonnet_gcmodule::ObjectSpace;17use jrsonnet_stdlib::ContextInitializer;18mod common;19use common::ContextInitializer as TestContextInitializer;2021fn run(file: &Path, root: &Path) -> String {22 let mut s = State::builder();2324 let std_context = ContextInitializer::new(PathResolver::Relative(root.to_owned()));25 26 std_context.add_ext_str("var1".into(), "test".into());27 std_context28 .add_ext_code("var2", "{x:1,y:2}")29 .expect("code is valid");3031 32 std_context33 .add_ext_code("codeVar", "3+3")34 .expect("code is valid");35 std_context.add_ext_str("stringVar".into(), "2 + 2".into());36 std_context37 .add_ext_code(38 "selfRecursiveVar",39 r#"[42, std.extVar("selfRecursiveVar")[0] + 1]"#,40 )41 .expect("code is valid");42 std_context43 .add_ext_code(44 "mutuallyRecursiveVar1",45 r#"[42, std.extVar("mutuallyRecursiveVar2")[0] + 1]"#,46 )47 .expect("code is valid");48 std_context49 .add_ext_code(50 "mutuallyRecursiveVar2",51 r#"[42, std.extVar("mutuallyRecursiveVar1")[0] + 1]"#,52 )53 .expect("code is valid");5455 s.context_initializer((std_context, TestContextInitializer))56 .import_resolver(FileImportResolver::default());57 let s = s.build();5859 let _entered = s.enter();6061 let trace_format = CompactFormat {62 resolver: PathResolver::FileName,63 max_trace: 20,64 padding: 4,65 };6667 let mut v = match s.import(file) {68 Ok(v) => v,69 Err(e) => return trace_format.format(&e).unwrap(),70 };7172 if file73 .file_name()74 .expect("file has basename")75 .to_str()76 .expect("jsonnet testsuite has ascii names")77 .starts_with("tla.")78 {79 let mut args = FxHashMap::new();80 args.insert(IStr::from("var1"), TlaArg::String("test".into()));81 args.insert(82 IStr::from("var2"),83 TlaArg::Val({84 let mut o = ObjValueBuilder::new();8586 o.field("x").value(Val::num(1));87 o.field("y").value(Val::num(2));8889 Val::Obj(o.build())90 }),91 );92 v = apply_tla(&args, v).expect("failed to apply tla");93 } else {94 v = match apply_tla(&FxHashMap::new(), v) {95 Ok(v) => v,96 Err(e) => return trace_format.format(&e).unwrap(),97 };98 }99100 match v.manifest(JsonFormat::default()) {101 Ok(v) => v,102 Err(e) => trace_format.format(&e).unwrap(),103 }104}105106fn read_file(path: &Path) -> io::Result<Option<String>> {107 match fs::read_to_string(path) {108 Ok(v) => Ok(Some(v)),109 Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),110 Err(e) => Err(e),111 }112}113114const SKIPPED: &[&str] = &[115 116117 118 119 "error.parse.deep_array_nesting.jsonnet",120 121 "error.parse.object_local_clash.jsonnet",122 "error.function_duplicate_param.jsonnet",123 124 "error.recursive_object_non_term.jsonnet",125 126 "error.trace_one_param.jsonnet",127 128 "error.trace_two_param.jsonnet",129 130 "invariant_manifest.jsonnet",131 132 "trace.jsonnet",133 134135 136 "bitwise_or9.jsonnet",137 138 "builtinBase64_string_high_codepoint.jsonnet",139 140 "builtinSplitLimitR6.jsonnet",141 142 "builtin_escapeStringJson.jsonnet",143 144 "builtin_manifestTomlEx.jsonnet",145 "div3.jsonnet",146 "pow6.jsonnet",147 148 "builtin_manifestYamlDoc.jsonnet",149 150 "multi.jsonnet",151 "multi_no_newline.jsonnet",152 "multi_no_newline_string_output.jsonnet",153 "multi_string_output.jsonnet",154 155 "native1.jsonnet",156 "native2.jsonnet",157 "native3.jsonnet",158 "native6.jsonnet",159 160 "number_leading_zero.jsonnet",161 162 "std.makeArray_recursive_evalutation_order_matters.jsonnet",163 164 "tailstrict3.jsonnet",165 166 "number_times_string.jsonnet",167 168 "string_times_number.jsonnet",169];170171#[test]172fn cpp_test_suite() -> io::Result<()> {173 for root_dir in ["cpp_test_suite", "go_testdata"] {174 let root_tests = PathBuf::from(env!("CARGO_MANIFEST_DIR"));175 let root = root_tests.join(root_dir);176 let root_override = root_tests.join(format!("{root_dir}_golden_override"));177178 for entry in fs::read_dir(&root).map_err(|e| io::Error::other(format!("failed to enumerate cpp_test_suite dir (Note: it needs to be cloned from C++ jsonnet repo for this test): {e}")))? {179 let entry = entry?;180 if entry.path().extension().is_none_or(|e| e != "jsonnet") {181 continue;182 }183184 let _stack = if entry.path().file_stem().is_some_and(|e| e == "recursive_function" || e == "tailstrict"|| e == "tailstrict5") {185 Some(limit_stack_depth(100_000))186 } else {187 None188 };189190 if entry191 .path()192 .file_name()193 .and_then(|v| v.to_str())194 .is_some_and(|v| SKIPPED.contains(&v))195 {196 continue;197 }198199 eprintln!("test: {}", entry.path().display());200201 let result = run(&entry.path(), &root);202203 let mut golden_path = entry.path();204 golden_path.set_extension("jsonnet.golden");205206 let mut golden_path2 = entry.path();207 golden_path2.set_extension("golden");208209 let golden_override =210 root_override.join(golden_path.file_name().expect("file has basename"));211212 213 let mut golden = read_file(&golden_path)?;214 215 if golden.is_none() && let Some(golden_path) = read_file(&golden_path2)? {216 golden = Some(golden_path);217 }218219 220 if let Some(golden_path) = read_file(&golden_override)? {221 golden = Some(golden_path);222 }223224 225 let golden = golden.unwrap_or_else(|| "true".to_owned());226227 let update_golden_path = &golden_override;228229 match (serde_json::from_str::<serde_json::Value>(&result), serde_json::from_str::<serde_json::Value>(&golden)) {230 (Err(_), Ok(_)) => panic!(231 "unexpected error for golden {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",232 entry.path().display()233 ),234 (Ok(_), Err(_)) => panic!(235 "expected error for golden {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",236 entry.path().display()237 ),238 (Ok(result_v), Ok(golden_v)) => {239 if result_v != golden_v {240 if env::var_os("UPDATE_GOLDEN").is_some() {241 fs::write(update_golden_path, result)?;242 } else {243 panic!(244 "Result \n{result_v:#}\n\245 and golden \n{golden_v:#}\n\246 did not match structurally\n\247 for golden {}",248 entry.path().display()249 );250 }251 }252 }253 (Err(_), Err(_)) => {254 if result != golden.trim_end() {255 if env::var_os("UPDATE_GOLDEN").is_some() {256 fs::write(update_golden_path, result)?;257 } else {258 panic!(259 "golden didn't match for {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",260 entry.path().display()261 )262 }263 }264 }265 }266 println!("done!");267 }268 }269270 jrsonnet_gcmodule::with_thread_object_space(ObjectSpace::leak);271272 Ok(())273}