git.delta.rocks / jrsonnet / refs/commits / d5225b820ddc

difftreelog

source

tests/tests/cpp_test_suite.rs7.8 KiBsourcehistory
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	// C++ test suite26	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	// Golang test suite32	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	// C++ tests:116117	// Parser fails with stack overflow. While is a bug, this is a too unusual118	// thing to run untrusted jsonnet code? Will be fixed with nom/rowan.119	"error.parse.deep_array_nesting.jsonnet",120	// Runtime, not static error in jrsonnet121	"error.parse.object_local_clash.jsonnet",122	"error.function_duplicate_param.jsonnet",123	// Too slow to throw due to how lazyness is implemented in jrsonnet124	"error.recursive_object_non_term.jsonnet",125	// In jrsonnet returns the one passed argument, works as Rust's dbg!()126	"error.trace_one_param.jsonnet",127	// In jrsonnet can display any value128	"error.trace_two_param.jsonnet",129	// Depends on unsafe handling of strings as arrays in jsonnet stdlib130	"invariant_manifest.jsonnet",131	// Little bit hard to capture trace logs in this test suite at this moment132	"trace.jsonnet",133	// Go tests:134135	// Something is wrong, go-jsonnet skips safe integer range check here136	"bitwise_or9.jsonnet",137	// Jrsonnet does not use byte strings, all utf8 is converted to bytes first138	"builtinBase64_string_high_codepoint.jsonnet",139	// Split by empty string is string characters, same as everywhere else140	"builtinSplitLimitR6.jsonnet",141	// escapeStringJson only accepts string in jrsonnet142	"builtin_escapeStringJson.jsonnet",143	// golang float formatting is inefficient and not portable144	"builtin_manifestTomlEx.jsonnet",145	"div3.jsonnet",146	"pow6.jsonnet",147	// golang escapes "e" yaml key, does it think it is float?148	"builtin_manifestYamlDoc.jsonnet",149	// multi output is a CLI part, not an interpreter.150	"multi.jsonnet",151	"multi_no_newline.jsonnet",152	"multi_no_newline_string_output.jsonnet",153	"multi_string_output.jsonnet",154	// Tested otherwise155	"native1.jsonnet",156	"native2.jsonnet",157	"native3.jsonnet",158	"native6.jsonnet",159	// Since when parser should throw an error for that?..160	"number_leading_zero.jsonnet",161	// Golang fails with max stack frames exceeded error162	"std.makeArray_recursive_evalutation_order_matters.jsonnet",163	// Tailstrict semantics is partially unspecified164	"tailstrict3.jsonnet",165	// Jrsonnet has this overload166	"number_times_string.jsonnet",167	// Jrsonnet has this overload168	"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		// .jsonnet.golden for C++ tests213		let mut golden = read_file(&golden_path)?;214		// .golden for Go tests215		if golden.is_none() && let Some(golden_path) = read_file(&golden_path2)? {216			golden = Some(golden_path);217		}218219		// Any of them can be overriden by overrides220		if let Some(golden_path) = read_file(&golden_override)? {221			golden = Some(golden_path);222		}223224		// Otherwise assume test should just not fail and return true.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}