From 577e1aa8db76d24991e09045954bd6c6c297e82a Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi <35780660+anakrish@users.noreply.github.com> Date: Sat, 16 Dec 2023 17:50:59 -0800 Subject: [PATCH] More OPA conformance; in-progress: ability to trace interpreter (#63) Signed-off-by: Anand Krishnamoorthi --- Cargo.toml | 1 + examples/regorus.rs | 5 + scripts/pre-push | 2 +- src/builtins/deprecated.rs | 2 + src/builtins/numbers.rs | 93 +++++++-- src/builtins/semver.rs | 15 +- src/builtins/strings.rs | 43 ++++- src/engine.rs | 1 + src/interpreter.rs | 176 ++++++++++++++---- src/number.rs | 6 + src/utils.rs | 71 +++++++ src/value.rs | 3 +- tests/aci/main.rs | 13 +- .../cases/builtins/comparison/tests.yaml | 44 ++--- .../cases/builtins/numbers/range.yaml | 22 ++- tests/interpreter/mod.rs | 86 +-------- tests/opa.passing | 13 +- tests/opa.rs | 4 +- tests/value/mod.rs | 2 +- 19 files changed, 429 insertions(+), 173 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 575d9cf7..fcac7d25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ wax = { version = "0.6.0", features = [], default-features = false, optional = t [dev-dependencies] clap = { version = "4.4.7", features = ["derive"] } +colored-diff = "0.2.3" serde_yaml = "0.9.16" test-generator = "0.3.1" walkdir = "2.3.2" diff --git a/examples/regorus.rs b/examples/regorus.rs index 8cc2d699..23dd3e9e 100644 --- a/examples/regorus.rs +++ b/examples/regorus.rs @@ -157,6 +157,11 @@ struct Cli { } fn main() -> Result<()> { + env_logger::builder() + .format_level(false) + .format_timestamp(None) + .init(); + // Parse and dispatch command. let cli = Cli::parse(); match cli.command { diff --git a/scripts/pre-push b/scripts/pre-push index f48b7e24..98774d1b 100755 --- a/scripts/pre-push +++ b/scripts/pre-push @@ -16,5 +16,5 @@ if [ -f Cargo.toml ]; then fi # Ensure that OPA conformance tests don't regress. - cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing) + cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing) fi diff --git a/src/builtins/deprecated.rs b/src/builtins/deprecated.rs index 6e7d6af5..df139397 100644 --- a/src/builtins/deprecated.rs +++ b/src/builtins/deprecated.rs @@ -73,6 +73,7 @@ fn cast_array(span: &Span, params: &[Ref], args: &[Value], strict: bool) - ensure_args_count(span, name, params, args, 1)?; match &args[0] { Value::Array(_) => Ok(args[0].clone()), + Value::Set(s) => Ok(Value::from_array(s.iter().cloned().collect())), _ if strict => bail!(params[0].span().error("array required")), _ => Ok(Value::Undefined), } @@ -113,6 +114,7 @@ fn cast_set(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> ensure_args_count(span, name, params, args, 1)?; match &args[0] { Value::Set(_) => Ok(args[0].clone()), + Value::Array(a) => Ok(Value::from_set(a.iter().cloned().collect())), _ if strict => bail!(params[0].span().error("set required")), _ => Ok(Value::Undefined), } diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index bc2428e1..f810a6e1 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -18,6 +18,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("ceil", (ceil, 1)); m.insert("floor", (floor, 1)); m.insert("numbers.range", (range, 2)); + m.insert("numbers.range_step", (range_step, 3)); m.insert("rand.intn", (intn, 2)); m.insert("round", (round, 1)); } @@ -29,6 +30,7 @@ pub fn arithmetic_operation( expr2: &Expr, v1: Value, v2: Value, + strict: bool, ) -> Result { let op_name = format!("{:?}", op).to_lowercase(); let v1 = ensure_numeric(op_name.as_str(), expr1, &v1)?; @@ -38,9 +40,11 @@ pub fn arithmetic_operation( ArithOp::Add => v1.add(&v2)?, ArithOp::Sub => v1.sub(&v2)?, ArithOp::Mul => v1.mul(&v2)?, - ArithOp::Div if v2 == Number::from(0u64) => bail!(span.error("divide by zero")), + ArithOp::Div if strict && v2 == Number::from(0u64) => bail!(span.error("divide by zero")), + ArithOp::Div if v2 == Number::from(0u64) => return Ok(Value::Undefined), ArithOp::Div => v1.divide(&v2)?, - ArithOp::Mod if v2 == Number::from(0u64) => bail!(span.error("modulo by zero")), + ArithOp::Mod if strict && v2 == Number::from(0u64) => bail!(span.error("modulo by zero")), + ArithOp::Mod if v2 == Number::from(0u64) => return Ok(Value::Undefined), ArithOp::Mod if !v1.is_integer() || !v2.is_integer() => { bail!(span.error("modulo on floating-point number")) } @@ -69,25 +73,33 @@ fn floor(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re )) } -fn range(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { - ensure_args_count(span, "numbers.range", params, args, 2)?; - let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0].clone())?; - let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1].clone())?; - - let (incr, num_elements) = match (v1.as_i64(), v2.as_i64()) { - (Some(v1), Some(v2)) if v2 > v1 => (1, v2 + 1 - v1), - (Some(v1), Some(v2)) => (-1, v1 + 1 - v2), - _ => { - // TODO: OPA returns undefined here. - // Can we emit a warning? - return Ok(Value::Undefined); - } - }; +fn range(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "numbers.range"; + ensure_args_count(span, name, params, args, 2)?; + let v1 = ensure_numeric(name, ¶ms[0], &args[0].clone())?; + let v2 = ensure_numeric(name, ¶ms[1], &args[1].clone())?; - let mut values = Vec::with_capacity(num_elements as usize); + match v1.is_integer() { + false if strict => bail!(params[0].span().error("operand must be integer")), + false => return Ok(Value::Undefined), + _ => (), + } + match v2.is_integer() { + false if strict => bail!(params[1].span().error("operand must be integer")), + false => return Ok(Value::Undefined), + _ => (), + } + + let (incr, num_elements) = match v2.sub(&v1)?.as_i64() { + Some(v) if v >= 0 => (1i64, v as usize + 1), + Some(v) => (-1i64, (-v + 1) as usize), + _ => bail!(span.error("could not determine number of elements")), + }; + + let mut values = Vec::with_capacity(num_elements); let mut v = v1; - let incr = Number::from(incr as i64); + let incr = Number::from(incr); while v != v2 { values.push(Value::from(v.clone())); v.add_assign(&incr)?; @@ -96,10 +108,51 @@ fn range(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re Ok(Value::from_array(values)) } +fn range_step(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "numbers.range_step"; + ensure_args_count(span, name, params, args, 3)?; + let v1 = ensure_numeric(name, ¶ms[0], &args[0].clone())?; + let v2 = ensure_numeric(name, ¶ms[1], &args[1].clone())?; + let incr = ensure_numeric(name, ¶ms[2], &args[2].clone())?; + + match v1.is_integer() { + false if strict => bail!(params[0].span().error("operand must be integer")), + false => return Ok(Value::Undefined), + _ => (), + } + + match v2.is_integer() { + false if strict => bail!(params[1].span().error("operand must be integer")), + false => return Ok(Value::Undefined), + _ => (), + } + + if strict && (!incr.is_integer() || incr <= Number::from(0u64)) { + bail!(params[2].span().error("step must be a positive integer")) + } + + let (incr, num_elements) = match (v2.sub(&v1)?.as_i64(), incr.as_i64()) { + (Some(v), Some(incr)) if v >= 0 => (incr, v / incr + 1), + (Some(v), Some(incr)) => (-incr, -v / incr + 1), + _ => bail!(span.error("could not determine number of elements")), + }; + + let mut values = Vec::with_capacity(num_elements as usize); + let incr = Number::from(incr); + let mut v = v1; + while (v <= v2 && incr.is_positive()) || (v >= v2 && !incr.is_positive()) { + values.push(Value::from(v.clone())); + v.add_assign(&incr)?; + } + + Ok(Value::from_array(values)) +} + fn round(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { - ensure_args_count(span, "round", params, args, 1)?; + let name = "round"; + ensure_args_count(span, name, params, args, 1)?; Ok(Value::from( - ensure_numeric("round", ¶ms[0], &args[0])?.round(), + ensure_numeric(name, ¶ms[0], &args[0])?.round(), )) } diff --git a/src/builtins/semver.rs b/src/builtins/semver.rs index b34a8631..65ed3793 100644 --- a/src/builtins/semver.rs +++ b/src/builtins/semver.rs @@ -35,9 +35,18 @@ fn compare(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Ok(Value::from(result as i64)) } -fn is_valid(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { +fn is_valid(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { let name = "semver.is_valid"; ensure_args_count(span, name, params, args, 1)?; - let v = ensure_string(name, ¶ms[0], &args[0])?; - Ok(Value::Bool(Version::parse(&v).is_ok())) + Ok(Value::Bool( + Version::parse(&if strict { + ensure_string(name, ¶ms[0], &args[0])? + } else { + match &args[0] { + Value::String(s) => s.clone(), + _ => return Ok(Value::Bool(false)), + } + }) + .is_ok(), + )) } diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index 1835ec20..dac7c33c 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -157,6 +157,41 @@ fn split(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Re )) } +fn to_string(v: &Value, unescape: bool) -> String { + match v { + Value::Null => "null".to_owned(), + Value::Bool(b) => b.to_string(), + Value::String(s) if unescape => serde_json::to_string(&s).unwrap_or(s.as_ref().to_string()), + Value::String(s) => s.as_ref().to_string(), + Value::Number(n) => n.format_decimal(), + Value::Array(a) => { + "[".to_owned() + + &a.iter() + .map(|e| to_string(e, true)) + .collect::>() + .join(", ") + + "]" + } + Value::Set(s) => { + "{".to_owned() + + &s.iter() + .map(|e| to_string(e, true)) + .collect::>() + .join(", ") + + "}" + } + Value::Object(o) => { + "{".to_owned() + + &o.iter() + .map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true)) + .collect::>() + .join(", ") + + "}" + } + Value::Undefined => "#undefined".to_string(), + } +} + fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "sprintf"; ensure_args_count(span, name, params, args, 2)?; @@ -219,6 +254,9 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> // Handle Golang printing verbs. // https://pkg.go.dev/fmt match (verb, arg) { + ('s', Value::String(sv)) => s += sv.as_ref(), + ('s', v) => s += &to_string(v, false), + ('v', _) => s += format!("{arg}").as_str(), ('b', Value::Number(f)) if f.is_integer() => { let (sign, v) = get_sign_value(f); @@ -315,11 +353,6 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> bail!(args_span.error("floating-point number specified for format verb {verb}.")); } - ('s', Value::String(sv)) => s += sv.as_ref(), - ('s', _) => { - bail!(args_span.error("invalid non string argument specified for format verb %s")); - } - ('+', _) if chars.next() == Some('v') => { bail!(args_span.error("Go-syntax fields names format verm %#v is not supported.")); } diff --git a/src/engine.rs b/src/engine.rs index 10a826e7..e94aaf0d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -147,6 +147,7 @@ impl Engine { self.interpreter.set_current_module(prev_module)?; } + self.interpreter.create_rule_prefixes()?; Ok(self.interpreter.get_data_mut().clone()) } diff --git a/src/interpreter.rs b/src/interpreter.rs index 79ab3227..2b441494 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -114,7 +114,7 @@ impl Interpreter { module: None, schedule: None, current_module_path: String::default(), - input: Value::Null, + input: Value::Undefined, data: Value::new_object(), init_data: Value::new_object(), with_document: Value::new_object(), @@ -288,7 +288,11 @@ impl Interpreter { Value::String(s) => s.to_string(), _ => index.to_string(), }; - let ref_path = ref_path + "." + &index + "." + &path.join(""); + let ref_path = if path.is_empty() { + ref_path + "." + &index + } else { + ref_path + "." + &index + "." + &path.join(".") + }; self.ensure_rule_evaluated(ref_path)?; } } @@ -483,7 +487,15 @@ impl Interpreter { (ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => { builtins::sets::difference(lhs, rhs, lhs_value, rhs_value) } - _ => builtins::numbers::arithmetic_operation(span, op, lhs, rhs, lhs_value, rhs_value), + _ => builtins::numbers::arithmetic_operation( + span, + op, + lhs, + rhs, + lhs_value, + rhs_value, + self.strict_builtin_errors, + ), } } @@ -998,6 +1010,7 @@ impl Interpreter { )?, _ => self.eval_expr(expr)?, }; + if let Some(ctx) = self.contexts.last_mut() { if let Some(result) = &mut ctx.result { result @@ -1011,14 +1024,10 @@ impl Interpreter { Literal::SomeVars { span, vars, .. } => { for var in vars { let name = var.source_str(); - if let Ok(variable) = self.add_variable_or(&name) { - if variable != Value::Undefined { - return Err(anyhow!( - "duplicated definition of local variable {}", - name - )); - } + if self.current_scope()?.get(&name).is_some() { + bail!("duplicated definition of local variable {}", name); } + self.add_variable_or(&name)?; } if let Some(ctx) = self.contexts.last_mut() { if let Some(result) = &mut ctx.result { @@ -1064,6 +1073,13 @@ impl Interpreter { } fn eval_stmt(&mut self, stmt: &LiteralStmt, stmts: &[&LiteralStmt]) -> Result { + debug_new_group!( + "eval_stmt {}:{} {}", + stmt.span.line, + stmt.span.col, + stmt.span.text() + ); + let mut skip_exec = false; let saved_state = if !stmt.with_mods.is_empty() { // Save state; @@ -1185,6 +1201,9 @@ impl Interpreter { if loop_expr_value[&idx] != Value::Undefined { result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result; return Ok(result); + } else if idx != Value::Undefined { + // The index is not valid for this collection. + return Ok(false); } } @@ -1240,11 +1259,8 @@ impl Interpreter { result = false; } _ => { - return Err(loop_expr.span.source.error( - loop_expr.span.line, - loop_expr.span.col, - "item cannot be indexed", - )); + // The item is not a collection. + result = false; } } @@ -1854,6 +1870,7 @@ impl Interpreter { self.default_rules.get(&fcn_path).cloned() } }; + if let Some(rules) = rules { for (rule, _) in rules.iter() { if let Rule::Default { value, .. } = rule.as_ref() { @@ -1967,6 +1984,7 @@ impl Interpreter { fn lookup_var(&mut self, span: &Span, fields: &[&str], no_error: bool) -> Result { let name = span.source_str(); + debug_new_group!("lookup_var: name={name}, fields={fields:?}, no_error={no_error}"); // Return local variable/argument. if let Some(v) = self.lookup_local_var(&name) { return Ok(Self::get_value_chained(v, fields)); @@ -1992,6 +2010,7 @@ impl Interpreter { // If the rule has already been evaluated or specified via a with modifier, // use that value. if v != Value::Undefined { + debug!("returning v = {v}"); return Ok(v); } @@ -2051,6 +2070,13 @@ impl Interpreter { } fn eval_expr(&mut self, expr: &ExprRef) -> Result { + debug_new_group!( + "eval_expr: {}:{} {}", + expr.span().line, + expr.span().col, + expr.span().text() + ); + match expr.as_ref() { Expr::Null(_) => Ok(Value::Null), Expr::True(_) => Ok(Value::Bool(true)), @@ -2292,7 +2318,7 @@ impl Interpreter { comps.push(*v.text()); expr = None; } - _ => bail!("internal error: not a simple ref"), + _ => bail!(format!("internal error: not a simplee ref {expr:?}")), } } if let Some(d) = document { @@ -2650,11 +2676,110 @@ impl Interpreter { } } + fn get_rule_path_components(mut refr: &Ref) -> Result>> { + let mut components: Vec> = vec![]; + loop { + refr = match refr.as_ref() { + Expr::Var(v) => { + components.push((*v.text().as_ref()).into()); + break; + } + Expr::RefBrack { refr, index, .. } => { + if let Expr::String(s) = index.as_ref() { + components.push((*s.text().as_ref()).into()); + } + refr + } + Expr::RefDot { refr, field, .. } => { + components.push((*field.text().as_ref()).into()); + refr + } + _ => break, + } + } + components.reverse(); + Ok(components) + } + + pub fn create_rule_prefixes(&mut self) -> Result<()> { + debug_new_group!("create_rule_prefixes"); + debug!("data before: {}", self.data); + + for module in self.modules.clone() { + let module_path = Self::get_rule_path_components(&module.package.refr)?; + debug!("processing module {module_path:?}"); + + for rule in &module.policy { + let mut rule_refr = Self::get_rule_refr(rule); + debug!("rule refr: {}", rule_refr.span().text()); + debug!("rule : {:?}", rule); + if let Rule::Spec { + head: + RuleHead::Set { + refr, key: None, .. + }, + .. + } = rule.as_ref() + { + rule_refr = match refr.as_ref() { + Expr::RefDot { refr, .. } => refr, + + _ => refr, + } + } + + let mut prefix_path = module_path.clone(); + prefix_path.append(&mut Self::get_rule_path_components(rule_refr)?); + let prefix_path: Vec<&str> = prefix_path[0..prefix_path.len() - 1] + .iter() + .map(|s| s.as_ref()) + .collect(); + + if Self::get_value_chained(self.data.clone(), &prefix_path) == Value::Undefined { + self.update_data( + rule_refr.span(), + rule_refr, + &prefix_path, + Value::new_object(), + )?; + } + } + } + debug!("data after: {}", self.data); + Ok(()) + } + + fn record_rule(&mut self, refr: &Ref, rule: Ref) -> Result<()> { + let path = get_root_var(refr)?; + let path = path.text(); + let path = self.current_module_path.clone() + "." + path; + match self.rules.entry(path) { + Entry::Occupied(o) => { + o.into_mut().push(rule.clone()); + } + Entry::Vacant(v) => { + v.insert(vec![rule.clone()]); + } + } + let path = Self::get_path_string(refr, None)?; + let path = self.current_module_path.clone() + "." + &path; + match self.rules.entry(path) { + Entry::Occupied(o) => { + o.into_mut().push(rule.clone()); + } + Entry::Vacant(v) => { + v.insert(vec![rule.clone()]); + } + } + Ok(()) + } + pub fn gather_rules(&mut self) -> Result<()> { for module in self.modules.clone() { let prev_module = self.set_current_module(Some(module.clone()))?; for rule in &module.policy { let refr = Self::get_rule_refr(rule); + if let Rule::Spec { .. } = rule.as_ref() { // Adjust refr to ensure simple ref. // TODO: refactor. @@ -2667,18 +2792,7 @@ impl Interpreter { Expr::RefBrack { refr, .. } => refr, _ => refr, }; - //let path = Self::get_path_string(refr, None)?; - let path = get_root_var(refr)?; - let path = path.text(); - let path = self.current_module_path.clone() + "." + path; - match self.rules.entry(path) { - Entry::Occupied(o) => { - o.into_mut().push(rule.clone()); - } - Entry::Vacant(v) => { - v.insert(vec![rule.clone()]); - } - } + self.record_rule(refr, rule.clone())?; } else if let Rule::Default { .. } = rule.as_ref() { let (refr, index) = match refr.as_ref() { // TODO: Validate the index @@ -2698,10 +2812,8 @@ impl Interpreter { _ => (refr, None), }; - //let path = Self::get_path_string(refr, None)?; - let path = get_root_var(refr)?; - let path = path.text(); - let path = self.current_module_path.clone() + "." + path; + let path = Self::get_path_string(refr, None)?; + let path = self.current_module_path.clone() + "." + &path; match self.default_rules.entry(path) { Entry::Occupied(o) => { for (_, i) in o.get() { diff --git a/src/number.rs b/src/number.rs index 9804ef04..8d46b196 100644 --- a/src/number.rs +++ b/src/number.rs @@ -282,6 +282,12 @@ impl Number { } } + pub fn is_positive(&self) -> bool { + match self { + Big(b) => b.d.is_sign_positive(), + } + } + fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> { match (a, b) { (Big(a), Big(b)) if a.d.is_integer() && b.d.is_integer() => { diff --git a/src/utils.rs b/src/utils.rs index 4ab6577c..b2fa956e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,6 +9,77 @@ use std::collections::BTreeMap; use anyhow::{bail, Result}; +#[cfg(debug_assertions)] +macro_rules! debug { + ($($arg:tt)+) => { + { + if log::log_enabled!(log::Level::Debug) { + print!("{}:{}:", file!(), line!()); + crate::utils::NESTING.with(|f| { + print!("{}", " ".repeat(*f.borrow() as usize)); + }); + println!($($arg)+); + } + } + } + +} + +#[cfg(not(debug_assertions))] +macro_rules! debug { + ($($arg:tt)+) => {}; +} + +#[allow(unused)] +pub(crate) use debug; + +#[cfg(debug_assertions)] +#[allow(unused)] +macro_rules! debug_new_group { + ($($arg:tt)+) => { + debug!($($arg)+); + let _group = DebugNesting::new(); + }; +} + +#[cfg(not(debug_assertions))] +macro_rules! debug_new_group { + ($($arg:tt)+) => {}; +} + +#[allow(unused)] +pub(crate) use debug_new_group; + +#[allow(unused)] +pub struct DebugNesting {} + +#[cfg(debug_assertions)] +thread_local!(pub static NESTING: std::cell::RefCell = std::cell::RefCell::new(1)); + +impl DebugNesting { + #[cfg(debug_assertions)] + #[allow(unused)] + pub fn new() -> DebugNesting { + NESTING.with(|f| { + *f.borrow_mut() += 1; + }); + DebugNesting {} + } +} + +#[allow(unused)] +impl Drop for DebugNesting { + #[cfg(debug_assertions)] + fn drop(&mut self) { + NESTING.with(|f| { + *f.borrow_mut() -= 1; + }); + } + + #[cfg(not(debug_assertions))] + fn drop(&mut self) {} +} + pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result { let mut comps: Vec<&str> = vec![]; let mut expr = Some(refr); diff --git a/src/value.rs b/src/value.rs index ef9ad5b5..219291b5 100644 --- a/src/value.rs +++ b/src/value.rs @@ -25,11 +25,12 @@ pub enum Value { Number(Number), String(Rc), Array(Rc>), - Object(Rc>), // Extra rego data type Set(Rc>), + Object(Rc>), + // Indicate that a value is undefined Undefined, } diff --git a/tests/aci/main.rs b/tests/aci/main.rs index 338f0c6c..dac562a4 100644 --- a/tests/aci/main.rs +++ b/tests/aci/main.rs @@ -85,11 +85,14 @@ fn run_aci_tests(dir: &Path) -> Result<()> { println!("passed {:?}", duration); } Ok(actual) => { - println!("failed {:?}", duration); - println!("ACTUAL:"); - println!("{}", serde_json::to_string(&actual)?); - println!("EXPECTED"); - println!("{}", serde_json::to_string(&case.want_result)?); + println!( + "DIFF {}", + colored_diff::PrettyDifference { + expected: &serde_yaml::to_string(&case.want_result)?, + actual: &serde_yaml::to_string(&actual)? + } + ); + nfailures += 1; } Err(e) => { diff --git a/tests/interpreter/cases/builtins/comparison/tests.yaml b/tests/interpreter/cases/builtins/comparison/tests.yaml index 3260acca..afb6edd4 100644 --- a/tests/interpreter/cases/builtins/comparison/tests.yaml +++ b/tests/interpreter/cases/builtins/comparison/tests.yaml @@ -214,8 +214,8 @@ cases: 0, "", [], + set(), {}, - set() ] results = { @@ -241,35 +241,35 @@ cases: lt: object!: - key: null - value: [false, 0, "", [], {}, {"set!":[]}] + value: [false, 0, "", [], {"set!":[]}, {}] - key: false - value: [0, "", [], {}, {"set!":[]}] + value: [0, "", [], {"set!":[]}, {}] - key: 0 - value: ["", [], {}, {"set!":[]}] + value: ["", [], {"set!":[]}, {}] - key: "" - value: [[], {}, {"set!":[]}] + value: [[], {"set!":[]}, {}] - key: [] - value: [{}, {"set!":[]}] - - key: {} - value: [{"set!":[]}] + value: [{"set!":[]}, {}] - key: {"set!":[]} + value: [{}] + - key: {} value: [] le: object!: - key: null - value: [null, false, 0, "", [], {}, {"set!":[]}] + value: [null, false, 0, "", [], {"set!":[]}, {}] - key: false - value: [false, 0, "", [], {}, {"set!":[]}] + value: [false, 0, "", [], {"set!":[]}, {}] - key: 0 - value: [0, "", [], {}, {"set!":[]}] + value: [0, "", [], {"set!":[]}, {}] - key: "" - value: ["", [], {}, {"set!":[]}] + value: ["", [], {"set!":[]}, {}] - key: [] - value: [[], {}, {"set!":[]}] - - key: {} - value: [{}, {"set!":[]}] + value: [[], {"set!":[]}, {}] - key: {"set!":[]} - value: [{"set!":[]}] + value: [{"set!":[]}, {}] + - key: {} + value: [{}] gt: object!: - key: null @@ -282,10 +282,10 @@ cases: value: [null, false, 0] - key: [] value: [null, false, 0, ""] - - key: {} - value: [null, false, 0, "", []] - key: {"set!":[]} - value: [null, false, 0, "", [], {}] + value: [null, false, 0, "", []] + - key: {} + value: [null, false, 0, "", [], {"set!":[]}] ge: object!: - key: null @@ -298,10 +298,10 @@ cases: value: [null, false, 0, ""] - key: [] value: [null, false, 0, "", []] - - key: {} - value: [null, false, 0, "", [], {}] - key: {"set!":[]} - value: [null, false, 0, "", [], {}, {"set!":[]}] + value: [null, false, 0, "", [], {"set!":[]}] + - key: {} + value: [null, false, 0, "", [], {"set!":[]}, {}] - note: arrays data: {} diff --git a/tests/interpreter/cases/builtins/numbers/range.yaml b/tests/interpreter/cases/builtins/numbers/range.yaml index 4f728bfe..b2cf609c 100644 --- a/tests/interpreter/cases/builtins/numbers/range.yaml +++ b/tests/interpreter/cases/builtins/numbers/range.yaml @@ -15,10 +15,6 @@ cases: # Single item range r5 = numbers.range(8, 8) - # Non-integer start and end result in Undefined. - r6 = numbers.range(1.01, 5) - r7 = numbers.range(1, 5.01) - y { false } r8 = numbers.range(y, 10) r9 = numbers.range(10, y) @@ -31,6 +27,24 @@ cases: r4: [-5, -4, -3, -2, -1] r5: [8] + - note: non integer start + data: {} + modules: + - | + package test + r6 = numbers.range(1.01, 5) + error: must be integer + query: data + + - note: non integer start + data: {} + modules: + - | + package test + r7 = numbers.range(1, 5.01) + error: must be integer + query: data + - note: less-args data: {} modules: diff --git a/tests/interpreter/mod.rs b/tests/interpreter/mod.rs index 37cf5010..49ab413f 100644 --- a/tests/interpreter/mod.rs +++ b/tests/interpreter/mod.rs @@ -68,85 +68,17 @@ pub fn process_value(v: &Value) -> Result { } } -fn display_values(c: &Value, e: &Value) -> Result { - Ok(format!( - "\nleft = {}\nright = {}\n", - serde_json::to_string_pretty(c)?, - serde_json::to_string_pretty(e)? - )) -} - -// Helper function to match computed and expecte values. -// On mismatch, prints the failing sub-value instead of the whole value. -fn match_values_impl(computed: &Value, expected: &Value) -> Result<()> { - match (&computed, &expected) { - (Value::Array(a1), Value::Array(a2)) => { - if a1.len() != a2.len() { - bail!( - "array length mismatch: {} != {}{}", - a1.len(), - a2.len(), - display_values(computed, expected)? - ); - } - - for (idx, v1) in a1.iter().enumerate() { - match_values_impl(v1, &a2[idx])?; - } - Ok(()) - } - - (Value::Set(s1), Value::Set(s2)) => { - if s1.len() != s2.len() { - bail!( - "set length mismatch: {} != {}{}", - s1.len(), - s2.len(), - display_values(computed, expected)? - ); - } - - let mut itr2 = s2.iter(); - for v1 in s1.iter() { - match_values_impl(v1, itr2.next().unwrap())?; - } - Ok(()) - } - - (Value::Object(o1), Value::Object(o2)) => { - if o1.len() != o2.len() { - bail!( - "object length mismatch: {} != {}{}", - o1.len(), - o2.len(), - display_values(computed, expected)? - ); - } - - let mut itr2 = o2.iter(); - for (k1, v1) in o1.iter() { - let (k2, v2) = itr2.next().unwrap(); - match_values_impl(k1, k2)?; - match_values_impl(v1, v2)?; - } - Ok(()) - } - - (Value::Number(n1), Value::Number(n2)) if n1 == n2 => Ok(()), - (Value::String(s1), Value::String(s2)) if s1 == s2 => Ok(()), - (Value::Bool(b1), Value::Bool(b2)) if b1 == b2 => Ok(()), - (Value::Null, Value::Null) => Ok(()), - (Value::Undefined, Value::Undefined) => Ok(()), - - _ => bail!("value mismatch: {}", display_values(computed, expected)?), - } -} - fn match_values(computed: &Value, expected: &Value) -> Result<()> { - match match_values_impl(computed, expected) { - Ok(()) => Ok(()), - Err(e) => bail!("\nmismatch in {}{}", display_values(computed, expected)?, e), + if computed != expected { + panic!( + "{}", + colored_diff::PrettyDifference { + expected: &serde_yaml::to_string(&expected)?, + actual: &serde_yaml::to_string(&computed)? + } + ); } + Ok(()) } pub fn check_output(computed_results: &[Value], expected_results: &[Value]) -> Result<()> { diff --git a/tests/opa.passing b/tests/opa.passing index 69768f43..87ff1446 100644 --- a/tests/opa.passing +++ b/tests/opa.passing @@ -25,6 +25,7 @@ cryptomd5 cryptosha1 cryptosha256 dataderef +defaultkeyword disjunction elsekeyword embeddedvirtualdoc @@ -44,7 +45,10 @@ invalidkeyerror jsonfilteridempotent jwtencodesignheadererrors jwtencodesignpayloaderrors +negation nestedreferences +numbersrange +numbersrangestep objectfilter objectfilteridempotent objectfilternonstringkey @@ -55,6 +59,7 @@ objectremoveidempotent objectremovenonstringkey partialdocconstants partialsetdoc +planner-ir rand regexfind regexfindallstringsubmatch @@ -65,9 +70,13 @@ regexreplace regexsplit replacen semvercompare +semverisvalid sets +sprintf subset +toarray topdowndynamicdispatch +toset trim trimleft trimprefix @@ -79,4 +88,6 @@ typebuiltin typenamebuiltin undos union -units \ No newline at end of file +units +varreferences +virtualdocs \ No newline at end of file diff --git a/tests/opa.rs b/tests/opa.rs index f93b3801..208ab24f 100644 --- a/tests/opa.rs +++ b/tests/opa.rs @@ -145,7 +145,9 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> { println!("\n{} failed.", case.note); println!("{}", serde_yaml::to_string(&case)?); match &r { - Ok(actual) => println!("GOT\n{}", serde_yaml::to_string(&actual)?), + Ok(actual) => { + println!("GOT\n{}", serde_yaml::to_string(&actual)?); + } Err(e) => println!("ERROR: {e}"), } diff --git a/tests/value/mod.rs b/tests/value/mod.rs index ba11fc3d..f252bfa8 100644 --- a/tests/value/mod.rs +++ b/tests/value/mod.rs @@ -43,8 +43,8 @@ fn non_string_key() -> Result<()> { "false": null, "3.141592653589793": null, "[true,null,3.141592653589793]": null, - "{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"\\\"\":null}": null, "[false,true,3.141592653589793]": null, + "{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"\\\"\":null}": null, "\"\"": null }"#;