More library functions (#51)

- units.parse, units.parse_bytes
- json.is_valid, json.marshal, json.unmarshal
- yaml.is_valid, yaml.marshal, yaml.unmarshal
- object.subset
- set_diff

* Also print number of errors due to each missing function
* Also lock down fully passing OPA suites

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-11-22 14:19:23 -08:00
committed by GitHub
parent e838d5af65
commit 4db2270dcf
14 changed files with 650 additions and 31 deletions

View File

@@ -32,3 +32,6 @@ jobs:
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl
- name: Run tests (MUSL)
run: cargo test --verbose --target x86_64-unknown-linux-musl
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -32,4 +32,4 @@ debug = true
[[test]]
name="opa"
harness=false
test=false
test=false

View File

@@ -14,4 +14,7 @@ if [ -f Cargo.toml ]; then
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
scripts/coverage
fi
# Ensure that OPA conformance tests don't regress.
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
fi

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins::utils::ensure_args_count;
use crate::builtins::utils::{ensure_args_count, ensure_set};
use crate::builtins::BuiltinFcn;
use crate::lexer::Span;
use crate::value::Value;
@@ -19,7 +19,7 @@ lazy_static! {
m.insert("all", (all, 1));
m.insert("any", (any, 1));
m.insert("set_diff", (set_diff, 2));
m
};
}
@@ -49,3 +49,11 @@ fn any(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}
}))
}
fn set_diff(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "set_diff";
ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_set(name, &params[0], args[0].clone())?;
let s2 = ensure_set(name, &params[1], args[1].clone())?;
Ok(Value::from_set(s1.difference(&s2).cloned().collect()))
}

View File

@@ -9,11 +9,17 @@ use crate::value::Value;
use std::collections::HashMap;
use anyhow::Result;
use anyhow::{Context, Result};
use data_encoding::BASE64;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("base64.decode", (base64_decode, 1));
m.insert("json.is_valid", (json_is_valid, 1));
m.insert("json.marshal", (json_marshal, 1));
m.insert("jsonunmarshal", (json_unmarshal, 1));
m.insert("yaml.is_valid", (yaml_is_valid, 1));
m.insert("yaml.marshal", (yaml_marshal, 1));
m.insert("yaml.unmarshal", (yaml_unmarshal, 1));
}
fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
@@ -26,3 +32,51 @@ fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
String::from_utf8_lossy(&decoded_bytes).to_string(),
))
}
fn yaml_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.is_valid";
ensure_args_count(span, name, params, args, 1)?;
let yaml_str = ensure_string(name, &params[0], &args[0])?;
Ok(Value::Bool(Value::from_yaml_str(&yaml_str).is_ok()))
}
fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.marshal";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
serde_yaml::to_string(&args[0])
.with_context(|| span.error("could not serialize to yaml"))?,
))
}
fn yaml_unmarshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "yaml.unmarshal";
ensure_args_count(span, name, params, args, 1)?;
let yaml_str = ensure_string(name, &params[0], &args[0])?;
Value::from_yaml_str(&yaml_str).with_context(|| span.error("could not deserialize yaml."))
}
fn json_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "json.is_valid";
ensure_args_count(span, name, params, args, 1)?;
let json_str = ensure_string(name, &params[0], &args[0])?;
Ok(Value::Bool(Value::from_json_str(&json_str).is_ok()))
}
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "json.marshal";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
serde_json::to_string(&args[0])
.with_context(|| span.error("could not serialize to json"))?,
))
}
fn json_unmarshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "json.unmarshal";
ensure_args_count(span, name, params, args, 1)?;
let json_str = ensure_string(name, &params[0], &args[0])?;
Value::from_json_str(&json_str).with_context(|| span.error("could not deserialize json."))
}

View File

@@ -17,6 +17,7 @@ mod strings;
mod time;
mod tracing;
pub mod types;
mod units;
mod utils;
use crate::ast::{Expr, Ref};
@@ -65,7 +66,7 @@ lazy_static! {
//opa::register(&mut m);
debugging::register(&mut m);
tracing::register(&mut m);
units::register(&mut m);
m
};
}

View File

@@ -20,6 +20,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("object.get", (get, 3));
m.insert("object.keys", (keys, 1));
m.insert("object.remove", (remove, 2));
m.insert("object.subset", (subset, 2));
}
fn json_filter_impl(v: &Value, filter: &Value) -> Value {
@@ -202,3 +203,31 @@ fn remove(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::Object(obj))
}
fn is_subset(sup: &Value, sub: &Value) -> bool {
match (sup, sub) {
(Value::Object(sup), Value::Object(sub)) => {
sub.iter().all(|(k, vsub)| {
match sup.get(k) {
// Some(vsup @ Value::Object(_)) => is_subset(vsup, vsub),
Some(vsup) => is_subset(vsup, vsub),
_ => false,
}
})
}
(Value::Set(sup), Value::Set(sub)) => sub.is_subset(sup),
(Value::Array(sup), Value::Array(sub)) => sup.windows(sub.len()).any(|w| w == &sub[..]),
(Value::Array(sup), Value::Set(_)) => {
let sup = Value::from_set(sup.iter().cloned().collect());
is_subset(&sup, sub)
}
(sup, sub) => sup == sub,
}
}
fn subset(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "object.subset";
ensure_args_count(span, name, params, args, 2)?;
Ok(Value::Bool(is_subset(&args[0], &args[1])))
}

162
src/builtins/units.rs Normal file
View File

@@ -0,0 +1,162 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_string};
use crate::lexer::Span;
use crate::value::{Float, Number, Value};
use std::collections::HashMap;
use anyhow::{bail, Context, Result};
use ordered_float::OrderedFloat;
pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("units.parse", (parse, 1));
m.insert("units.parse_bytes", (parse_bytes, 1));
}
fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "units.parse";
ensure_args_count(span, name, params, args, 1)?;
let string = ensure_string(name, &params[0], &args[0])?;
let string = string.as_str();
// Remove quotes.
let string = if string.starts_with('"') && string.ends_with('"') && string.len() >= 2 {
&string[1..string.len() - 1]
} else {
string
};
// Disallow whitespace.
if string.chars().any(char::is_whitespace) {
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
_ => (string, ""),
};
let n: Float = if number_part.starts_with('.') {
serde_json::from_str(format!("0{number_part}").as_str())
} else {
serde_json::from_str(number_part)
}
.with_context(|| span.error("could not parse number"))?;
Ok(Value::Number(Number(OrderedFloat(
n * 10f64.powf(match suffix {
"E" | "e" => 18,
"P" | "p" => 15,
"T" | "t" => 12,
"G" | "g" => 9,
"M" => 6,
"K" | "k" => 3,
"m" => -3,
// The following are not supported by OPA
"Q" => 30,
"R" => 27,
"Y" => 24,
"Z" => 21,
"h" => 2,
"da" => 1,
"d" => -1,
"c" => -2,
"μ" => -6,
"n" => -9,
"f" => -15,
"a" => -18,
"z" => -21,
"y" => -24,
"r" => -27,
"q" => -30,
// No suffix specified.
"" => 0,
_ => {
return Ok(Value::Number(Number(OrderedFloat(
n * 2f64.powf(match suffix.to_ascii_lowercase().as_str() {
"ki" => 10,
"mi" => 20,
"gi" => 30,
"ti" => 40,
"pi" => 50,
"ei" => 60,
"zi" => 70,
"yi" => 80,
_ => return Ok(Value::Undefined),
} as f64),
))));
}
} as f64),
))))
}
fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
let name = "units.parse_bytes";
ensure_args_count(span, name, params, args, 1)?;
let string = ensure_string(name, &params[0], &args[0])?;
let string = string.as_str();
// Remove quotes.
let string = if string.starts_with('"') && string.ends_with('"') && string.len() >= 2 {
&string[1..string.len() - 1]
} else {
string
};
// Disallow whitespace.
if string.chars().any(char::is_whitespace) {
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
_ => (string, ""),
};
let n: Float = if number_part.starts_with('.') {
serde_json::from_str(format!("0{number_part}").as_str())
} else {
serde_json::from_str(number_part)
}
.with_context(|| span.error("could not parse number"))?;
Ok(Value::Number(Number(OrderedFloat(f64::round(
n * 2f64.powf(match suffix.to_ascii_lowercase().as_str() {
"yi" | "yib" => 80,
"zi" | "zib" => 70,
"ei" | "eib" => 60,
"pi" | "pib" => 50,
"ti" | "tib" => 40,
"gi" | "gib" => 30,
"mi" | "mib" => 20,
"ki" | "kib" => 10,
"" => 0,
_ => {
return Ok(Value::Number(Number(OrderedFloat(
n * 10f64.powf(match suffix.to_ascii_lowercase().as_str() {
"q" | "qb" => 30,
"r" | "rb" => 27,
"y" | "yb" => 24,
"z" | "zb" => 21,
"e" | "eb" => 18,
"p" | "pb" => 15,
"t" | "tb" => 12,
"g" | "gb" => 9,
"m" | "mb" => 6,
"k" | "kb" => 3,
_ => {
return Ok(Value::Undefined);
}
} as f64),
))))
}
} as f64),
)))))
}

View File

@@ -65,6 +65,7 @@ impl Default for QueryResult {
#[derive(Debug, Clone, Default, Serialize)]
pub struct QueryResults {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub result: Vec<QueryResult>,
}
@@ -75,6 +76,7 @@ struct Context {
value: Value,
result: Option<QueryResult>,
results: QueryResults,
is_compr: bool,
}
#[derive(Debug)]
@@ -494,7 +496,7 @@ impl Interpreter {
// Omit recording undefined values.
if value == Value::Undefined {
return Ok(Value::Bool(false));
return Ok(value); //Ok(Value::Bool(false));
}
self.add_variable_or(&name)?;
@@ -527,6 +529,7 @@ impl Interpreter {
value: Value::new_set(),
result: None,
results: QueryResults::default(),
is_compr: false,
});
let mut r = true;
match domain {
@@ -842,9 +845,14 @@ impl Interpreter {
if let Some(ctx) = self.contexts.last_mut() {
if let Some(result) = &mut ctx.result {
result
.expressions
.push(Self::make_expression_result(span, &value))
if value != Value::Undefined {
result
.expressions
.push(Self::make_expression_result(span, &value))
} else {
result.bindings = Value::new_object();
result.expressions.clear();
}
}
}
@@ -1165,9 +1173,9 @@ impl Interpreter {
Value::Set(ref mut s) => {
Rc::make_mut(s).insert(output);
}
_ => bail!("internal error: invalid context value"),
a => bail!("internal error: invalid context value {a}"),
}
} else {
} else if !ctx.is_compr {
match &ctx.value {
Value::Set(_) => (),
_ => ctx.value = Value::Undefined,
@@ -1191,7 +1199,11 @@ impl Interpreter {
.insert(Value::String(name.to_string()), value.clone());
}
}
ctx.results.result.push(result);
if result.expressions.iter().all(|v| v != &Value::Undefined)
&& !result.expressions.is_empty()
{
ctx.results.result.push(result);
}
}
return Ok(true);
@@ -1306,7 +1318,11 @@ impl Interpreter {
.insert(Value::String(name.to_string()), value.clone());
}
}
ctx.results.result.push(result);
if result.expressions.iter().all(|v| v != &Value::Undefined)
&& !result.expressions.is_empty()
{
ctx.results.result.push(result);
}
}
}
@@ -1428,6 +1444,7 @@ impl Interpreter {
value: Value::new_array(),
result: None,
results: QueryResults::default(),
is_compr: true,
});
// Evaluate body first.
@@ -1447,6 +1464,7 @@ impl Interpreter {
value: Value::new_set(),
result: None,
results: QueryResults::default(),
is_compr: true,
});
self.eval_query(query)?;
@@ -1470,6 +1488,7 @@ impl Interpreter {
value: Value::new_object(),
result: None,
results: QueryResults::default(),
is_compr: true,
});
self.eval_query(query)?;
@@ -1615,6 +1634,7 @@ impl Interpreter {
value: Value::new_set(),
result: None,
results: QueryResults::default(),
is_compr: false,
};
// Back up local variables of current function and empty
@@ -1734,7 +1754,6 @@ impl Interpreter {
}
// Evaluate the associated default rules after non-default rules
if let Some(rules) = self.default_rules.get(&path) {
dbg!(&path);
for (r, _) in rules.clone() {
if !self.processed.contains(&r) {
let module = self.get_rule_module(&r)?;
@@ -1826,7 +1845,12 @@ impl Interpreter {
)),
},
// TODO: Handle string vs rawstring
Expr::String(span) => Ok(Value::String(span.text().to_string())),
Expr::String(span) => {
match serde_json::from_str::<Value>(format!("\"{}\"", span.text()).as_str()) {
Ok(s) => Ok(s),
Err(e) => bail!(span.error(format!("invalid string literal. {e}").as_str())),
}
}
Expr::RawString(span) => Ok(Value::String(span.text().to_string())),
// TODO: Handle undefined variables
@@ -1884,6 +1908,7 @@ impl Interpreter {
value,
result: None,
results: QueryResults::default(),
is_compr: false,
},
path,
))
@@ -1897,6 +1922,7 @@ impl Interpreter {
value: Value::new_set(),
result: None,
results: QueryResults::default(),
is_compr: false,
},
path,
))
@@ -1938,6 +1964,7 @@ impl Interpreter {
value: Value::new_array(),
result: None,
results: QueryResults::default(),
is_compr: false,
});
}
result = self.eval_query(&body.query);
@@ -2018,10 +2045,10 @@ impl Interpreter {
}
}
pub fn merge_value(span: &Span, value: &mut Value, new: Value) -> Result<()> {
pub fn merge_rule_value(span: &Span, value: &mut Value, new: Value) -> Result<()> {
match value.merge(new) {
Ok(()) => Ok(()),
Err(err) => return Err(span.error(format!("{err}").as_str())),
Err(_) => Err(span.error("rules should not produce multiple outputs.")),
}
}
@@ -2173,15 +2200,15 @@ impl Interpreter {
if let Value::Object(btree) = &vref {
if !btree.contains_key(&index) {
Self::merge_value(span, vref, object)?;
Self::merge_rule_value(span, vref, object)?;
}
} else if let Value::Undefined = vref {
Self::merge_value(span, vref, object)?;
Self::merge_rule_value(span, vref, object)?;
}
} else {
let vref = Self::make_or_get_value_mut(&mut self.data, &paths)?;
if let Value::Undefined = &vref {
Self::merge_value(span, vref, value)?;
Self::merge_rule_value(span, vref, value)?;
}
};
@@ -2204,7 +2231,7 @@ impl Interpreter {
// Ensure that path is created.
let vref = Self::make_or_get_value_mut(&mut self.data, path)?;
if Self::get_value_chained(self.init_data.clone(), path) == Value::Undefined {
Self::merge_value(span, vref, value)
Self::merge_rule_value(span, vref, value)
} else {
Err(span.error("value for rule has already been specified in data document"))
}
@@ -2408,6 +2435,7 @@ impl Interpreter {
// Request that results be gathered.
result: Some(QueryResult::default()),
results: QueryResults::default(),
is_compr: false,
});
let prev_module = self.set_current_module(self.modules.last().cloned())?;
@@ -2431,7 +2459,9 @@ impl Interpreter {
let orig_idx = ord[expr_idx] as usize;
ordered_expressions[orig_idx] = value.clone();
}
results.result[idx].expressions = ordered_expressions;
if !ordered_expressions.iter().any(|v| v == &Value::Undefined) {
results.result[idx].expressions = ordered_expressions;
}
}
}
self_schedule.order.remove(k);

View File

@@ -43,6 +43,7 @@ pub fn schedule<Str: Clone + std::cmp::Ord + std::fmt::Debug>(
empty: &Str,
) -> Result<SortResult> {
let num_statements = infos.len();
let orig_infos: Vec<&StmtInfo<Str>> = infos.iter().collect();
// Mapping from each var to the list of statements that define it.
let mut defining_stmts: BTreeMap<Str, Vec<usize>> = BTreeMap::new();
@@ -191,7 +192,10 @@ pub fn schedule<Str: Clone + std::cmp::Ord + std::fmt::Debug>(
}
if order.len() != num_statements {
bail!("could not schedule all statements {order:?} {num_statements}");
eprintln!("could not schedule all statements {order:?} {orig_infos:?}");
return Ok(SortResult::Order(
(0..num_statements).map(|i| i as u16).collect(),
));
}
// TODO: determine cycles.

View File

@@ -0,0 +1,153 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: all
data: {}
modules:
- |
package test
# Supportex by OPA
a = [ "E", "e", "P", "p", "T", "t" , "G" , "g" , "M" , "K" , "k" , "m" ]
# Not supported by OPA
b = [ "Q", "R", "Y", "Z", "h", "da", "d", "c",
"μ", "n", "f", "a", "z", "y", "r", "q"
]
c = [
"ki", "Ki", "kI", "KI",
"mi", "Mi", "mI", "MI",
"gi", "Gi", "gI", "GI",
"ti", "Ti", "tI", "TI",
"pi", "Pi", "pI", "PI",
"ei", "Ei", "eI", "EI",
]
d = [
"zi", "Zi", "zI", "ZI",
"yi", "Yi", "yI", "YI",
]
results = {
"p1" : [ units.parse(s) | s = concat("", ["1", a[_]]) ],
"p2" : [ units.parse(s) | s = concat("", ["1", b[_]]) ],
"p3" : [ units.parse(s) | s = concat("", ["1", c[_]]) ],
"p4" : [ units.parse(s) | s = concat("", ["1", d[_]]) ],
# No suffix, quoted.
"p5" : [ units.parse("1"), units.parse("\"1\"") ]
}
query: data.test.results
want_result:
p1:
- 1e18
- 1e18
- 1e15
- 1e15
- 1e12
- 1e12
- 1e9
- 1e9
- 1e6
- 1e3
- 1e3
- 1e-3
p2:
- 1e30
- 1e27
- 1e24
- 1e21
- 1e2
- 1e1
- 1e-1
- 1e-2
- 1e-6
- 1e-9
- 1e-15
- 1e-18
- 1e-21
- 1e-24
- 1e-27
- 1e-30
p3:
- 1024
- 1024
- 1024
- 1024
- 1048576
- 1048576
- 1048576
- 1048576
- 1073741824
- 1073741824
- 1073741824
- 1073741824
- 1099511627776
- 1099511627776
- 1099511627776
- 1099511627776
- 1125899906842624
- 1125899906842624
- 1125899906842624
- 1125899906842624
- 1152921504606846976
- 1152921504606846976
- 1152921504606846976
- 1152921504606846976
p4:
- 1.1805916207174113e21
- 1.1805916207174113e21
- 1.1805916207174113e21
- 1.1805916207174113e21
- 1.2089258196146292e24
- 1.2089258196146292e24
- 1.2089258196146292e24
- 1.2089258196146292e24
p5: [1, 1]
- note: extra argument
data: {}
modules:
- |
package test
a = units.parse("1m", "")
query: data.test
error: expects 1 argument
- note: zero arguments
data: {}
modules:
- |
package test
a = units.parse()
query: data.test
error: expects 1 argument
- note: array
data: {}
modules:
- |
package test
a = units.parse(["1"])
query: data.test
error: expects string argument
- note: space
data: {}
modules:
- |
package test
a = units.parse("1 m")
query: data.test
error: spaces not allowed in resource string
- note: b suffix not supported
data: {}
modules:
- |
package test
a = units.parse("1mb")
query: data.test
want_result: {}

View File

@@ -0,0 +1,115 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
cases:
- note: all
data: {}
modules:
- |
package test
a = [
"y",
"z",
"e",
"p",
"t",
"g",
"m",
"k",
]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "i"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "i", "b"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "i", "B"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "I"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "I", "b"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", a[_], "I", "B"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", upper(a[_]), "i", "b"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", upper(a[_]), "i", "B"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", upper(a[_]), "I", "b"])]
p1 = [ units.parse_bytes(s) | s = concat("", ["1", upper(a[_]), "I", "B"])]
b = [
"q",
"r",
"y",
"z",
"e",
"p",
"t",
"g",
"m",
"k",
]
p2 = [ units.parse_bytes(s) | s = concat("", ["1", b[_], "b"])]
p2 = [ units.parse_bytes(s) | s = concat("", ["1", b[_], "B"])]
p2 = [ units.parse_bytes(s) | s = concat("", ["1", upper(b[_]), "b"])]
p2 = [ units.parse_bytes(s) | s = concat("", ["1", upper(b[_]), "B"])]
results= {
"p1": p1,
"p2": p2,
}
query: data.test.results
want_result:
p1:
- 1.2089258196146292e24
- 1.1805916207174113e21
- 1152921504606846976
- 1125899906842624
- 1099511627776
- 1073741824
- 1048576
- 1024
p2:
- 1e30
- 1e27
- 1e24
- 1e21
- 1e18
- 1e15
- 1e12
- 1e9
- 1e6
- 1e3
- note: extra argument
data: {}
modules:
- |
package test
a = units.parse_bytes("1m", "")
query: data.test
error: expects 1 argument
- note: zero arguments
data: {}
modules:
- |
package test
a = units.parse_bytes()
query: data.test
error: expects 1 argument
- note: array
data: {}
modules:
- |
package test
a = units.parse_bytes(["1"])
query: data.test
error: expects string argument
- note: space
data: {}
modules:
- |
package test
a = units.parse_bytes("1 m")
query: data.test
error: spaces not allowed in resource string

42
tests/opa.passing Normal file
View File

@@ -0,0 +1,42 @@
aggregates
all
any
array
assignments
bitsand
bitsnegate
bitsor
bitsshiftright
bitsxor
comparisonexpr
completedoc
compositebasedereference
dataderef
embeddedvirtualdoc
evaltermexpr
example
fix1863
intersection
invalidkeyerror
jsonfilteridempotent
nestedreferences
objectfilteridempotent
objectfilternonstringkey
objectremoveidempotent
objectremovenonstringkey
partialsetdoc
rand
replacen
semvercompare
sets
subset
topdowndynamicdispatch
trim
trimleft
trimprefix
trimright
trimsuffix
typebuiltin
typenamebuiltin
union
units

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT License.
use regorus::*;
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
@@ -72,7 +72,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
let tests_path = Path::new(&opa_tests_dir);
let mut status = BTreeMap::<String, (u32, u32)>::new();
let mut n = 0;
let mut missing_functions = BTreeSet::new();
let mut missing_functions = BTreeMap::new();
for entry in WalkDir::new(&opa_tests_dir)
.sort_by_file_name()
.into_iter()
@@ -93,7 +93,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
continue;
}
let run_test = folders.is_empty() || folders.iter().any(|f| path_dir_str.contains(f));
let run_test = folders.is_empty() || folders.iter().any(|f| &path_dir_str == f);
if !run_test {
continue;
}
@@ -108,24 +108,35 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
(Ok(actual), Some(expected)) if &actual == expected => {
entry.0 += 1;
}
(Ok(actual), None)
if actual == Value::new_array()
&& case.want_error.is_none()
&& case.error.is_none() =>
{
entry.0 += 1;
}
(Err(_), None) if case.want_error.is_some() => {
// Expected failure.
entry.0 += 1;
}
(r, _) => {
print!("\n{} failed.", case.note);
dbg!((&case, &r));
if let Err(e) = r {
let msg = e.to_string();
let pat = "could not find function ";
if let Some(pos) = msg.find(pat) {
let fcn = &msg[pos + pat.len()..];
missing_functions.insert(fcn.to_string());
missing_functions
.entry(fcn.to_string())
.and_modify(|e| *e += 1)
.or_insert(1);
}
}
let path = Path::new("target/opa/failures").join(path_dir);
std::fs::create_dir_all(path.clone())?;
let mut cmd = "target/debug/examples/regorus eval".to_string();
let mut cmd = "cargo run --example dregorus eval".to_string();
if let Some(data) = &case.data {
let json_path = path.join(format!("data{n}.json"));
cmd += format!(" -d {}", json_path.display()).as_str();
@@ -163,7 +174,7 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
}
}
println!("\nTESTSUITE STATUS");
println!("\nOPA TESTSUITE STATUS");
println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL");
let (mut npass, mut nfail) = (0, 0);
for (dir, (pass, fail)) in status {
@@ -187,9 +198,13 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
if !missing_functions.is_empty() {
println!("\nMISSING FUNCTIONS");
for (idx, fcn) in missing_functions.iter().enumerate() {
println!("\x1b[31m {:4}: {fcn}\x1b[0m", idx + 1);
println!(" {:4} {:40} {}", "", "FUNCTION", "FAILURES");
let mut ncalls = 0;
for (idx, (fcn, calls)) in missing_functions.iter().enumerate() {
println!("\x1b[31m {:4}: {fcn:40} {calls}\x1b[0m", idx + 1);
ncalls += calls;
}
println!("\x1b[31m {:4} {:40} {ncalls}\x1b[0m", "", "TOTAL");
}
if nfail != 0 {