feat(azure_policy): test runner, compiler fixes, and example program (#700)

Adds the YAML test runner that exercises the companion test data PRs, plus
several compiler fixes surfaced during testing:

- Removed parameter register caching that produced wrong results inside
  short-circuiting allOf/anyOf blocks; added literal-index caching for
  parameter defaults to avoid repeated O(n) literal-table scans
- Simplified cross-resource effect details to only emit roleDefinitionIds
  and type (deployment templates are not evaluated for compliance)
- Replaced guid/uniqueString builtins with clear "unsupported" errors
- Normalized datetime output to ISO 8601 with Z suffix
- Added azure_policy parser MAX_COL constant (8192) for long template
  expressions, keeping the global DEFAULT_MAX_COL at 1024
- Added rvm to azure_policy feature dependencies since the compiler
  targets RVM bytecode

Also restructures the example binary into examples/regorus/ with new
azure-policy-eval and azure-policy-aliases subcommands, adds C# alias
normalization tests, and documents Azure Policy support in the README.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Anand Krishnamoorthi
2026-04-30 13:02:37 -05:00
committed by GitHub
parent 7f42115b63
commit 4c92fb4d92
24 changed files with 1555 additions and 191 deletions

View File

@@ -8,7 +8,7 @@
//! `"if"` / `"then"` structure. The test runner extracts the `"if"` constraint
//! JSON and parses it with `parse_constraint`.
use anyhow::Result;
use anyhow::{bail, Result};
use regorus::languages::azure_policy::parser;
use regorus::Source;
use serde::{Deserialize, Serialize};
@@ -64,8 +64,8 @@ fn should_run_test_case(case_note: &str) -> bool {
///
/// Returns `None` if parsing fails or there is no `"if"` key (the caller
/// should feed the raw string to `parse_constraint` for error tests).
fn extract_if_json(policy_rule_json: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_str(policy_rule_json).ok()?;
fn extract_if_json(source_json: &str) -> Option<String> {
let v: serde_json::Value = serde_json::from_str(source_json).ok()?;
let if_value = v.get("if")?;
Some(if_value.to_string())
}
@@ -102,12 +102,12 @@ fn yaml_test_impl(file: &str) -> Result<()> {
let expects_parse_error = case.want_parse_error == Some(true);
let input_json = if let Some(ref rule) = case.policy_rule {
let source_json = if let Some(ref rule) = case.policy_rule {
rule.clone()
} else if let Some(ref rule) = test.policy_rule {
rule.clone()
} else {
panic!("case '{}': must specify 'policy_rule'", case.note);
bail!("case '{}': must specify 'policy_rule'", case.note);
};
let parse_level = case.parse_level.as_deref().unwrap_or("constraint");
@@ -115,35 +115,33 @@ fn yaml_test_impl(file: &str) -> Result<()> {
let parse_result = match parse_level {
"policy_rule" => {
// Parse the full policy_rule JSON with parse_policy_rule.
let source = Source::from_contents(format!("test:{}", case.note), input_json)?;
let source = Source::from_contents(format!("test:{}", case.note), source_json)?;
parser::parse_policy_rule(&source).map(|_| ())
}
"policy_definition" => {
// Parse the full policy definition JSON with parse_policy_definition.
let source = Source::from_contents(format!("test:{}", case.note), input_json)?;
let source = Source::from_contents(format!("test:{}", case.note), source_json)?;
parser::parse_policy_definition(&source).map(|_| ())
}
"constraint" => {
// Extract the "if" constraint JSON. If extraction fails
// (malformed JSON or missing "if" key), feed the raw
// input to parse_constraint — it should fail,
// matching want_parse_error.
let constraint_json = match extract_if_json(&input_json) {
Some(json) => json,
None => input_json,
};
// (malformed JSON or missing "if" key), feed the raw source
// JSON to parse_constraint — it should fail, matching
// want_parse_error.
let constraint_json =
extract_if_json(&source_json).unwrap_or_else(|| source_json.clone());
let source = Source::from_contents(format!("test:{}", case.note), constraint_json)?;
parser::parse_constraint(&source).map(|_| ())
}
other => {
panic!("case '{}': unknown parse_level '{}'", case.note, other);
bail!("case '{}': unknown parse_level '{}'", case.note, other);
}
};
match parse_result {
Ok(()) => {
if expects_parse_error {
panic!(
bail!(
"case '{}': expected parse error but parsing succeeded",
case.note
);
@@ -154,7 +152,7 @@ fn yaml_test_impl(file: &str) -> Result<()> {
if expects_parse_error {
println!("passed (expected parse error: {})", e);
} else {
panic!("case '{}': unexpected parse error: {}", case.note, e);
bail!("case '{}': unexpected parse error: {}", case.note, e);
}
}
}