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 tla::TlaArg,13 trace::{CompactFormat, PathResolver, TraceFormat},14};15use jrsonnet_stdlib::ContextInitializer;16mod common;17use common::ContextInitializer as TestContextInitializer;1819fn run(file: &Path, root: &Path) -> String {20 let mut s = State::builder();2122 let std_context = ContextInitializer::new(PathResolver::Relative(root.to_owned()));23 24 std_context.add_ext_str("var1".into(), "test".into());25 std_context26 .add_ext_code("var2", "{x:1,y:2}")27 .expect("code is valid");2829 30 std_context31 .add_ext_code("codeVar", "3+3")32 .expect("code is valid");33 std_context.add_ext_str("stringVar".into(), "2 + 2".into());34 std_context35 .add_ext_code(36 "selfRecursiveVar",37 r#"[42, std.extVar("selfRecursiveVar")[0] + 1]"#,38 )39 .expect("code is valid");40 std_context41 .add_ext_code(42 "mutuallyRecursiveVar1",43 r#"[42, std.extVar("mutuallyRecursiveVar2")[0] + 1]"#,44 )45 .expect("code is valid");46 std_context47 .add_ext_code(48 "mutuallyRecursiveVar2",49 r#"[42, std.extVar("mutuallyRecursiveVar1")[0] + 1]"#,50 )51 .expect("code is valid");5253 s.context_initializer((std_context, TestContextInitializer))54 .import_resolver(FileImportResolver::default());55 let s = s.build();5657 let _entered = s.enter();5859 let trace_format = CompactFormat {60 resolver: PathResolver::FileName,61 max_trace: 20,62 padding: 4,63 };6465 let mut v = match s.import(file) {66 Ok(v) => v,67 Err(e) => return trace_format.format(&e).unwrap(),68 };6970 if file71 .file_name()72 .expect("file has basename")73 .to_str()74 .expect("jsonnet testsuite has ascii names")75 .starts_with("tla.")76 {77 let mut args = FxHashMap::new();78 args.insert(IStr::from("var1"), TlaArg::String("test".into()));79 args.insert(80 IStr::from("var2"),81 TlaArg::Val({82 let mut o = ObjValueBuilder::new();8384 o.field("x").value(Val::num(1));85 o.field("y").value(Val::num(2));8687 Val::Obj(o.build())88 }),89 );90 v = apply_tla(&args, v).expect("failed to apply tla");91 } else {92 v = match apply_tla(&FxHashMap::new(), v) {93 Ok(v) => v,94 Err(e) => return trace_format.format(&e).unwrap(),95 };96 }9798 match v.manifest(JsonFormat::default()) {99 Ok(v) => v,100 Err(e) => trace_format.format(&e).unwrap(),101 }102}103104fn read_file(path: &Path) -> io::Result<Option<String>> {105 match fs::read_to_string(path) {106 Ok(v) => Ok(Some(v)),107 Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),108 Err(e) => Err(e),109 }110}111112const SKIPPED: &[&str] = &[113 114115 116 117 "error.parse.deep_array_nesting.jsonnet",118 119 "error.parse.object_local_clash.jsonnet",120 "error.function_duplicate_param.jsonnet",121 122 "error.recursive_object_non_term.jsonnet",123 124 "error.trace_one_param.jsonnet",125 126 "error.trace_two_param.jsonnet",127 128 "invariant_manifest.jsonnet",129 130 "trace.jsonnet",131 132133 134 "bitwise_or9.jsonnet",135 136 "builtinBase64_string_high_codepoint.jsonnet",137 138 "builtinSplitLimitR6.jsonnet",139 140 "builtin_escapeStringJson.jsonnet",141 142 "builtin_manifestTomlEx.jsonnet",143 "div3.jsonnet",144 "pow6.jsonnet",145 146 "builtin_manifestYamlDoc.jsonnet",147 148 "multi.jsonnet",149 "multi_no_newline.jsonnet",150 "multi_no_newline_string_output.jsonnet",151 "multi_string_output.jsonnet",152 153 "native1.jsonnet",154 "native2.jsonnet",155 "native3.jsonnet",156 "native6.jsonnet",157 158 "number_leading_zero.jsonnet",159 160 "std.makeArray_recursive_evalutation_order_matters.jsonnet",161 162 "tailstrict3.jsonnet",163 164 "number_times_string.jsonnet",165 166 "string_times_number.jsonnet",167];168169#[test]170fn cpp_test_suite() -> io::Result<()> {171 for root_dir in ["cpp_test_suite", "go_testdata"] {172 let root_tests = PathBuf::from(env!("CARGO_MANIFEST_DIR"));173 let root = root_tests.join(root_dir);174 let root_override = root_tests.join(format!("{root_dir}_golden_override"));175176 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}")))? {177 let entry = entry?;178 if entry.path().extension().is_none_or(|e| e != "jsonnet") {179 continue;180 }181182 if entry183 .path()184 .file_name()185 .and_then(|v| v.to_str())186 .is_some_and(|v| SKIPPED.contains(&v))187 {188 continue;189 }190191 println!("test: {}", entry.path().display());192193 let result = run(&entry.path(), &root);194195 let mut golden_path = entry.path();196 golden_path.set_extension("jsonnet.golden");197198 let mut golden_path2 = entry.path();199 golden_path2.set_extension("golden");200201 let golden_override =202 root_override.join(golden_path.file_name().expect("file has basename"));203204 205 let mut golden = read_file(&golden_path)?;206 207 if golden.is_none() && let Some(golden_path) = read_file(&golden_path2)? {208 golden = Some(golden_path);209 }210211 212 if let Some(golden_path) = read_file(&golden_override)? {213 golden = Some(golden_path);214 }215216 217 #[cfg(feature = "ir-parser")]218 let ir_parser_override_path = {219 let p = root_tests220 .join(format!("{root_dir}_golden_override_ir_parser"))221 .join(golden_path.file_name().expect("file has basename"));222 if let Some(golden_path) = read_file(&p)? {223 golden = Some(golden_path);224 }225 p226 };227228 229 let golden = golden.unwrap_or_else(|| "true".to_owned());230231 #[cfg(feature = "ir-parser")]232 let update_golden_path = &ir_parser_override_path;233 #[cfg(not(feature = "ir-parser"))]234 let update_golden_path = &golden_override;235236 match (serde_json::from_str::<serde_json::Value>(&result), serde_json::from_str::<serde_json::Value>(&golden)) {237 (Err(_), Ok(_)) => panic!(238 "unexpected error for golden {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",239 entry.path().display()240 ),241 (Ok(_), Err(_)) => panic!(242 "expected error for golden {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",243 entry.path().display()244 ),245 (Ok(result_v), Ok(golden_v)) => {246 if result_v != golden_v {247 if env::var_os("UPDATE_GOLDEN").is_some() {248 fs::write(update_golden_path, result)?;249 } else {250 panic!(251 "Result \n{result_v:#}\n\252 and golden \n{golden_v:#}\n\253 did not match structurally\n\254 for golden {}",255 entry.path().display()256 );257 }258 }259 }260 (Err(_), Err(_)) => {261 if result != golden.trim_end() {262 if env::var_os("UPDATE_GOLDEN").is_some() {263 fs::write(update_golden_path, result)?;264 } else {265 panic!(266 "golden didn't match for {}:\n<got>\n{result}\n</got>\n<golden>\n{golden}\n</golden>",267 entry.path().display()268 )269 }270 }271 }272 }273 }274 }275276 Ok(())277}