More OPA conformant semantics (#62)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-12-11 09:00:06 -08:00
committed by GitHub
parent 8a73b4bef9
commit 73ee18f002
32 changed files with 542 additions and 205 deletions
+21 -3
View File
@@ -10,33 +10,50 @@ bitsor
bitsshiftleft
bitsshiftright
bitsxor
casts
comparisonexpr
completedoc
compositebasedereference
dataderef
comprehensions
containskeyword
cryptohmacequal
cryptohmacmd5
cryptohmacmd5
cryptohmacsha1
cryptohmacsha256
cryptohmacsha512
cryptomd5
cryptsha1
cryptosha1
cryptosha256
dataderef
disjunction
elsekeyword
embeddedvirtualdoc
evaltermexpr
every
example
fix1863
functionerrors
functions
globmatch
globquotemeta
helloworld
indexing
indirectreferences
intersection
invalidkeyerror
jsonfilteridempotent
jwtencodesignheadererrors
jwtencodesignpayloaderrors
nestedreferences
objectfilter
objectfilteridempotent
objectfilternonstringkey
objectget
objectkeys
objectremove
objectremoveidempotent
objectremovenonstringkey
partialdocconstants
partialsetdoc
rand
regexfind
@@ -57,6 +74,7 @@ trimprefix
trimright
trimspace
trimsuffix
type
typebuiltin
typenamebuiltin
undos
+26
View File
@@ -16,18 +16,33 @@ const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
const OPA_BRANCH: &str = "v0.58.0";
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
struct TestCase {
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
input: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
input_term: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
modules: Option<Vec<String>>,
note: String,
query: String,
#[serde(skip_serializing_if = "Option::is_none")]
sort_bindings: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
want_result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
skip: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
traces: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
strict_error: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
want_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
want_error_code: Option<String>,
}
@@ -45,11 +60,18 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
if let Some(input) = &case.input {
engine.set_input(input.clone());
}
if let Some(input_term) = &case.input_term {
let input = Value::from_json_str(&input_term)?;
engine.set_input(input);
}
if let Some(modules) = &case.modules {
for (idx, rego) in modules.iter().enumerate() {
engine.add_policy(format!("rego{idx}.rego"), rego.clone())?;
}
}
engine.set_strict_builtin_errors(case.strict_error.unwrap_or_default());
let query_results = engine.eval_query(case.query.clone(), true)?;
let mut values = vec![];
@@ -182,9 +204,11 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
println!("\nOPA TESTSUITE STATUS");
println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL");
let (mut npass, mut nfail) = (0, 0);
let mut passing = vec![];
for (dir, (pass, fail)) in status {
if fail == 0 {
println!("\x1b[32m {dir:40}: {pass:4} {fail:4}\x1b[0m");
passing.push(dir);
} else {
println!("\x1b[31m {dir:40}: {pass:4} {fail:4}\x1b[0m");
}
@@ -193,6 +217,8 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
}
println!();
std::fs::write("target/opa.passing", passing.join("\n"))?;
if npass == 0 && nfail == 0 {
bail!("no matching tests found.");
} else if nfail == 0 {
+2
View File
@@ -569,12 +569,14 @@ fn match_rule(r: &Rule, v: &Value) -> Result<()> {
Rule::Default {
span,
refr,
args,
op,
value,
} => {
let obj = &v["default"];
match_span_opt(span, &obj["span"])?;
match_expr(refr, &obj["refr"])?;
match_vec(span /*dummy*/, args, &obj["args"])?;
match_assign_op(span, op, &obj["op"])?;
match_expr(value, &obj["value"])
}
+6 -3
View File
@@ -37,9 +37,12 @@ cases:
r = 1
rrr = "fun"
scopes:
- locals: ["p", "y", "q", "x", "a", "b", "idx"]
- locals: ["x"]
unscoped: ["p", "y", "q", "a", "b", "idx"]
inputs: ["r", "rrr"]
- locals: ["k", "r1"]
- locals: []
unscoped: ["k", "r1"]
inputs: ["a", "idx", "rrr"]
- locals: ["q", "idx1", "t"]
- locals: ["q"]
unscoped: ["idx1", "t"]
inputs: ["rrr"]
+18 -4
View File
@@ -12,6 +12,7 @@ use std::collections::BTreeSet;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Scope {
pub locals: BTreeSet<String>,
pub unscoped: BTreeSet<String>,
pub inputs: BTreeSet<String>,
}
@@ -27,8 +28,11 @@ struct YamlTest {
cases: Vec<TestCase>,
}
fn to_string_set(s: &BTreeSet<SourceStr>) -> BTreeSet<String> {
s.iter().map(|s| s.to_string()).collect()
fn to_string_set<'a, I>(itr: I) -> BTreeSet<String>
where
I: std::iter::Iterator<Item = &'a SourceStr>,
{
itr.map(|s| s.to_string()).collect()
}
fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> {
@@ -55,8 +59,18 @@ fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> {
if idx > expected_scopes.len() {
bail!("extra scope generated.")
}
assert_eq!(to_string_set(&scope.locals), expected_scopes[idx].locals);
assert_eq!(to_string_set(&scope.inputs), expected_scopes[idx].inputs);
assert_eq!(
to_string_set(scope.locals.keys()),
expected_scopes[idx].locals
);
assert_eq!(
to_string_set(scope.unscoped.iter()),
expected_scopes[idx].unscoped
);
assert_eq!(
to_string_set(scope.inputs.iter()),
expected_scopes[idx].inputs
);
println!("scope {idx} matched.")
}