More OPA conformance; in-progress: ability to trace interpreter (#63)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-12-16 17:50:59 -08:00
committed by GitHub
parent 73ee18f002
commit 577e1aa8db
19 changed files with 428 additions and 172 deletions

View File

@@ -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"

View File

@@ -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 {

View File

@@ -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

View File

@@ -73,6 +73,7 @@ fn cast_array(span: &Span, params: &[Ref<Expr>], 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<Expr>], 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),
}

View File

@@ -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<Value> {
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<Expr>], args: &[Value], _strict: bool) -> Re
))
}
fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "numbers.range", params, args, 2)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0].clone())?;
let v2 = ensure_numeric("numbers.range", &params[1], &args[1].clone())?;
fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "numbers.range";
ensure_args_count(span, name, params, args, 2)?;
let v1 = ensure_numeric(name, &params[0], &args[0].clone())?;
let v2 = ensure_numeric(name, &params[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);
}
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 as usize);
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<Expr>], args: &[Value], _strict: bool) -> Re
Ok(Value::from_array(values))
}
fn range_step(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "numbers.range_step";
ensure_args_count(span, name, params, args, 3)?;
let v1 = ensure_numeric(name, &params[0], &args[0].clone())?;
let v2 = ensure_numeric(name, &params[1], &args[1].clone())?;
let incr = ensure_numeric(name, &params[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<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
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", &params[0], &args[0])?.round(),
ensure_numeric(name, &params[0], &args[0])?.round(),
))
}

View File

@@ -35,9 +35,18 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
Ok(Value::from(result as i64))
}
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "semver.is_valid";
ensure_args_count(span, name, params, args, 1)?;
let v = ensure_string(name, &params[0], &args[0])?;
Ok(Value::Bool(Version::parse(&v).is_ok()))
Ok(Value::Bool(
Version::parse(&if strict {
ensure_string(name, &params[0], &args[0])?
} else {
match &args[0] {
Value::String(s) => s.clone(),
_ => return Ok(Value::Bool(false)),
}
})
.is_ok(),
))
}

View File

@@ -157,6 +157,41 @@ fn split(span: &Span, params: &[Ref<Expr>], 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::<Vec<String>>()
.join(", ")
+ "]"
}
Value::Set(s) => {
"{".to_owned()
+ &s.iter()
.map(|e| to_string(e, true))
.collect::<Vec<String>>()
.join(", ")
+ "}"
}
Value::Object(o) => {
"{".to_owned()
+ &o.iter()
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
.collect::<Vec<String>>()
.join(", ")
+ "}"
}
Value::Undefined => "#undefined".to_string(),
}
}
fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "sprintf";
ensure_args_count(span, name, params, args, 2)?;
@@ -219,6 +254,9 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], 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<Expr>], 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."));
}

View File

@@ -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())
}

View File

@@ -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<bool> {
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<Value> {
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<Value> {
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<Expr>) -> Result<Vec<Rc<str>>> {
let mut components: Vec<Rc<str>> = 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<Expr>, rule: Ref<Rule>) -> 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() {

View File

@@ -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() => {

View File

@@ -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<u32> = 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<String> {
let mut comps: Vec<&str> = vec![];
let mut expr = Some(refr);

View File

@@ -25,11 +25,12 @@ pub enum Value {
Number(Number),
String(Rc<str>),
Array(Rc<Vec<Value>>),
Object(Rc<BTreeMap<Value, Value>>),
// Extra rego data type
Set(Rc<BTreeSet<Value>>),
Object(Rc<BTreeMap<Value, Value>>),
// Indicate that a value is undefined
Undefined,
}

View File

@@ -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) => {

View File

@@ -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: {}

View File

@@ -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:

View File

@@ -68,85 +68,17 @@ pub fn process_value(v: &Value) -> Result<Value> {
}
}
fn display_values(c: &Value, e: &Value) -> Result<String> {
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<()> {

View File

@@ -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
units
varreferences
virtualdocs

View File

@@ -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}"),
}

View File

@@ -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,\"\\\"<undefined>\\\"\":null}": null,
"[false,true,3.141592653589793]": null,
"{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"<undefined>\\\"\":null}": null,
"\"<undefined>\"": null
}"#;