// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use regorus::*; use std::collections::BTreeMap; use std::path::Path; use anyhow::Result; use walkdir::WalkDir; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct TestCase { data: Option, input: Option, modules: Option>, note: String, query: String, sort_bindings: Option, want_result: Option, skip: Option, error: Option, traces: Option, want_error: Option, want_error_code: Option, } #[derive(Serialize, Deserialize, PartialEq, Debug)] struct YamlTest { cases: Vec, } fn eval_test_case(case: &TestCase) -> Result { let mut engine = Engine::new(); if let Some(data) = &case.data { engine.add_data(data.clone())?; } if let Some(input) = &case.input { engine.set_input(input.clone()); } if let Some(modules) = &case.modules { for (idx, rego) in modules.iter().enumerate() { engine.add_policy(format!("rego{idx}.rego"), rego.clone())?; } } let query_results = engine.eval_query(case.query.clone(), true)?; let mut values = vec![]; for qr in query_results.result { values.push(if !qr.bindings.is_empty_object() { qr.bindings.clone() } else if let Some(v) = qr.expressions.last() { v["value"].clone() } else { Value::Undefined }); } let result = Value::from_array(values); // Make result json compatible. (E.g: avoid sets). Value::from_json_str(&result.to_string()) } #[test] fn run_opa_tests() -> Result<()> { let opa_tests_dir = match std::env::var("OPA_TESTS_DIR") { Ok(v) => v, _ => { println!("OPA_TESTS_DIR environment vairable not defined."); return Ok(()); } }; let tests_path = Path::new(&opa_tests_dir); let mut status = BTreeMap::::new(); let mut n = 0; for entry in WalkDir::new(&opa_tests_dir) .sort_by_file_name() .into_iter() .filter_map(|e| e.ok()) { let path_str = entry.path().to_string_lossy().to_string(); let path = Path::new(&path_str); if !path.is_file() || !path_str.ends_with(".yaml") { continue; } let path_dir = path.strip_prefix(tests_path)?.parent().unwrap(); let path_dir_str = path_dir.to_string_lossy().to_string(); let entry = status.entry(path_dir_str).or_insert((0, 0)); let yaml_str = std::fs::read_to_string(&path_str)?; let test: YamlTest = serde_yaml::from_str(&yaml_str)?; for case in &test.cases { print!("{} ...", case.note); match (eval_test_case(case), &case.want_result) { (Ok(actual), Some(expected)) if &actual == expected => { println!("passed"); entry.0 += 1; } (Err(_), None) if case.want_error.is_some() => { // Expected failure. println!("passed"); entry.0 += 1; } _ => { let path = Path::new("target/opa").join(path_dir); std::fs::create_dir_all(path.clone())?; if let Some(data) = &case.data { std::fs::write( path.join(format!("data{n}.json")), data.to_json_str()?.as_bytes(), )?; }; if let Some(input) = &case.input { std::fs::write( path.join(format!("input{n}.json")), input.to_json_str()?.as_bytes(), )?; }; if let Some(modules) = &case.modules { if modules.len() == 1 { std::fs::write( path.join(format!("rego{n}.rego")), modules[0].as_bytes(), )?; } else { for (i, m) in modules.iter().enumerate() { std::fs::write( path.join(format!("rego{n}_{i}.rego")), m.as_bytes(), )?; } } } std::fs::write(path.join(format!("query{n}.text")), case.query.as_bytes())?; println!("failed"); entry.1 += 1; n += 1; continue; } }; } } println!("TESTSUITE STATUS"); println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL"); let (mut npass, mut nfail) = (0, 0); for (dir, (pass, fail)) in status { if fail == 0 { println!("\x1b[32m {dir:40}: {pass:4} {fail:4}\x1b[0m"); } else { println!("\x1b[31m {dir:40}: {pass:4} {fail:4}\x1b[0m"); } npass += pass; nfail += fail; } println!(); println!("\x1b[31m {:40}: {npass:4} {nfail:4}\x1b[0m", "TOTAL"); Ok(()) }