Compare commits

..

1 Commits

Author SHA1 Message Date
Anthony Martin
0c789ba125 Add C# test examples 2025-04-16 18:28:25 +00:00
770 changed files with 8896 additions and 139088 deletions

View File

@@ -1,2 +0,0 @@
[alias]
xtask = "run --package xtask --"

View File

@@ -1,29 +0,0 @@
name: rust-toolchain
description: Setup Rust toolchain with specified version and components
inputs:
toolchain:
description: 'Rust toolchain version'
required: false
default: '1.92.0'
components:
description: 'Additional components to install'
required: false
default: 'clippy rustfmt'
targets:
description: 'Target architectures to install'
required: false
default: ''
runs:
using: composite
steps:
- shell: bash
run: |
rustup override set ${{ inputs.toolchain }}
if [ -n "${{ inputs.components }}" ]; then
rustup component add ${{ inputs.components }}
fi
if [ -n "${{ inputs.targets }}" ]; then
rustup target add ${{ inputs.targets }}
fi
cargo --version
rustc --version

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
@@ -7,95 +5,7 @@
version: 2
updates:
# All Rust/Cargo directories are grouped into a single entry so that
# when a dependency is updated, Dependabot bumps it across the root
# workspace AND every binding, preventing version skew.
- package-ecosystem: "cargo"
directories:
- "/"
- "/bindings/ffi"
- "/bindings/java"
- "/bindings/python"
- "/bindings/ruby"
- "/bindings/wasm"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
# Bundle all Cargo dependency updates into a single PR. Without this,
# dependabot creates a separate PR per directory for the same dependency,
# and each individual PR fails to build due to version skew.
rust-dependencies:
patterns:
- "*"
# Ignore vendored mimalloc crates; updates are managed manually.
ignore:
- dependency-name: "regorus-mimalloc"
- dependency-name: "regorus-mimalloc-sys"
- package-ecosystem: "gomod"
directory: "/bindings/go"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "maven"
directory: "/bindings/java"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "nuget"
directory: "/bindings/csharp"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "pip"
directory: "/bindings/python"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "bundler"
directory: "/bindings/ruby"
schedule:
interval: "weekly"
commit-message:
prefix: "build(deps)"
groups:
per-dependency:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "ci(deps)"
groups:
github-actions:
patterns:
- "*"

View File

@@ -1,193 +0,0 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: "CodeQL Security Analysis"
on:
schedule:
# Run weekly on Wednesdays at 3:17 AM UTC
- cron: '17 3 * * 3'
workflow_dispatch:
# Allow manual triggering
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
# required for all workflows
security-events: write
# required to fetch internal or private CodeQL packs
packages: read
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
# Rust analysis for main crate and Rust-based bindings
- language: rust
build-mode: none
working-directory: .
# C/C++ analysis for FFI bindings
- language: c-cpp
build-mode: manual
working-directory: bindings/ffi
# Python analysis for Python bindings
- language: python
build-mode: none
working-directory: bindings/python
# Java analysis for Java bindings
- language: java-kotlin
build-mode: manual
working-directory: bindings/java
# Go analysis for Go bindings
- language: go
build-mode: manual
working-directory: bindings/go
# C# analysis for C# bindings
- language: csharp
build-mode: manual
working-directory: bindings/csharp
# JavaScript analysis for WASM bindings
- language: javascript-typescript
build-mode: none
working-directory: bindings/wasm
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Setup language-specific dependencies BEFORE CodeQL init for proper tracing setup
- name: Setup Rust
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch workspace dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
if: matrix.language == 'c-cpp' || matrix.language == 'go' || matrix.language == 'csharp'
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Fetch Java crate dependencies
if: matrix.language == 'java-kotlin'
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- name: Setup Python
if: matrix.language == 'python'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.10'
- name: Setup Java
if: matrix.language == 'java-kotlin'
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'corretto'
java-version: '8'
- name: Setup Go
if: matrix.language == 'go'
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: '1.21'
- name: Setup .NET
if: matrix.language == 'csharp'
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: ./bindings/csharp/global.json
- name: Invoke dotnet directly
if: matrix.language == 'csharp'
run: dotnet --info
- name: Setup Node.js
if: matrix.language == 'javascript-typescript'
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '18'
- name: Initialize CodeQL
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
# Install additional build dependencies
- name: Install system dependencies
if: matrix.language == 'rust' || matrix.language == 'c-cpp'
run: |
sudo apt-get update
sudo apt-get install -y build-essential cmake
- name: Install Python build dependencies
if: matrix.language == 'python'
working-directory: ${{ matrix.working-directory }}
run: |
python -m pip install --upgrade pip
pip install maturin[patchelf] pytest
- name: Setup Ruby
if: matrix.language == 'rust' && contains(matrix.working-directory, 'ruby')
uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1.299.0
with:
ruby-version: '3.4.2'
bundler-cache: true
working-directory: bindings/ruby
- name: Install WASM build dependencies
if: matrix.language == 'javascript-typescript'
run: |
cargo install wasm-pack
# Manual build steps for different languages
- name: Build C/C++ bindings via xtask
if: matrix.language == 'c-cpp'
run: |
cargo xtask test-c --release --frozen
cargo xtask test-cpp --release --frozen --skip-ffi
cargo xtask test-c-no-std --release --frozen --skip-ffi
- name: Build Java bindings via xtask
if: matrix.language == 'java-kotlin'
run: cargo xtask test-java --release --frozen
- name: Build Go bindings via xtask
if: matrix.language == 'go'
run: cargo xtask test-go --release --frozen
- name: Build C# bindings manually
if: matrix.language == 'csharp'
working-directory: ${{ matrix.working-directory }}
run: |
# Temporary workaround: CodeQL's tracer replaces dotnet with a missing shim when cargo xtask test-csharp runs,
# so invoke dotnet directly here until the upstream fix lands.
# Ideal command once fixed: cargo xtask test-csharp --release
# Build the FFI library that C# bindings access via P/Invoke
cd ../ffi
cargo build --release --locked
cd ../csharp
# Restore NuGet packages and build .NET assemblies in release mode
dotnet restore Regorus/Regorus.csproj
dotnet build Regorus/Regorus.csproj --no-restore /p:Configuration=Release /p:IgnoreMissingArtifacts=true
- name: Build WASM bindings via xtask
if: matrix.language == 'javascript-typescript'
run: cargo xtask build-wasm --release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: "/language:${{matrix.language}}"

View File

@@ -1,129 +0,0 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: dependabot/refresh-cargo-lockfiles
on:
pull_request_target:
types: [opened, synchronize, reopened]
branches: ["main"]
concurrency:
group: dependabot-refresh-cargo-lockfiles-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
jobs:
refresh-cargo-lockfiles:
permissions:
contents: write
if: >-
github.event.pull_request.user.login == 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
persist-credentials: false
- name: Setup Rust toolchain
run: |
rustup toolchain install 1.92.0 --profile minimal
rustup override set 1.92.0
cargo --version
rustc --version
- name: Refresh affected Cargo lockfiles
shell: bash
run: |
set -euo pipefail
base_sha="${{ github.event.pull_request.base.sha }}"
head_sha="${{ github.event.pull_request.head.sha }}"
mapfile -t changed_files < <(git diff --name-only "$base_sha" "$head_sha" -- ':(glob)**/Cargo.toml' ':(glob)**/Cargo.lock')
if [ "${#changed_files[@]}" -eq 0 ]; then
echo "No Cargo manifest or lockfile changes detected."
exit 0
fi
declare -A manifests=()
for path in "${changed_files[@]}"; do
case "$path" in
bindings/ffi/*)
manifests["bindings/ffi/Cargo.toml"]=1
;;
bindings/java/*)
manifests["bindings/java/Cargo.toml"]=1
;;
bindings/python/*)
manifests["bindings/python/Cargo.toml"]=1
;;
bindings/ruby/*)
manifests["bindings/ruby/Cargo.toml"]=1
;;
bindings/wasm/*)
manifests["bindings/wasm/Cargo.toml"]=1
;;
*)
manifests["Cargo.toml"]=1
;;
esac
done
for manifest in "${!manifests[@]}"; do
echo "Refreshing lockfile for $manifest"
cargo metadata \
--config 'build.rustc="rustc"' \
--config 'build.rustc-wrapper=""' \
--config 'build.rustc-workspace-wrapper=""' \
--format-version 1 \
--all-features \
--manifest-path "$manifest" > /dev/null
done
if [[ -n "${manifests[Cargo.toml]+x}" ]]; then
echo "Refreshing lockfile for tests/ensure_no_std/Cargo.toml (thumbv7m-none-eabi)"
cargo metadata \
--config 'build.rustc="rustc"' \
--config 'build.rustc-wrapper=""' \
--config 'build.rustc-workspace-wrapper=""' \
--format-version 1 \
--manifest-path tests/ensure_no_std/Cargo.toml \
--filter-platform thumbv7m-none-eabi > /dev/null
fi
- name: Commit lockfile refresh
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mapfile -t lockfiles < <(git ls-files -m -o --exclude-standard -- ':(glob)**/Cargo.lock')
for lockfile in "${lockfiles[@]}"; do
git add "$lockfile"
done
if git diff --cached --quiet; then
echo "No Cargo lockfile changes required."
exit 0
fi
auth_header=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 | tr -d '\n')
trap 'git config --unset-all http.https://github.com/.extraheader' EXIT
git config http.https://github.com/.extraheader "AUTHORIZATION: basic ${auth_header}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git commit -m "build(deps): refresh Cargo lockfiles"
git push origin HEAD:${{ github.event.pull_request.head.ref }}

View File

@@ -1,66 +0,0 @@
name: Dependency Audits
on:
pull_request:
push:
branches: ["main"]
schedule:
- cron: "0 6 * * 1"
workflow_dispatch:
permissions:
contents: read
jobs:
cargo-audit:
name: Cargo Audit (${{ matrix.lockfile }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
lockfile:
- Cargo.lock
- bindings/ffi/Cargo.lock
- bindings/java/Cargo.lock
- bindings/python/Cargo.lock
- bindings/ruby/Cargo.lock
- bindings/wasm/Cargo.lock
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run cargo audit
uses: rustsec/audit-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
lockfile: ${{ matrix.lockfile }}
cargo-deny:
name: Cargo Deny (${{ matrix.manifest }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
manifest:
- Cargo.toml
- bindings/ffi/Cargo.toml
- bindings/java/Cargo.toml
- bindings/python/Cargo.toml
- bindings/ruby/Cargo.toml
- bindings/ruby/ext/regorusrb/Cargo.toml
- bindings/wasm/Cargo.toml
- tests/ensure_no_std/Cargo.toml
- xtask/Cargo.toml
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/toolchains/rust
- name: Run cargo deny
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check
command-arguments: advisories bans
manifest-path: ${{ matrix.manifest }}

View File

@@ -1,82 +0,0 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# Thorough weekly test of non-default feature combinations.
# Catches regressions from dependency updates and feature-gating issues
# that the fast PR CI checks (cargo check only) would miss at runtime.
name: tests/feature-matrix
on:
workflow_dispatch:
schedule:
# Run at 3:42 AM UTC every Saturday.
- cron: "42 3 * * 6"
env:
CARGO_TERM_COLOR: always
jobs:
feature-matrix:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# Bare minimum: validates that the core interpreter works
# without any builtins or optional subsystems.
- name: minimal (std + arc)
features: std,arc
# Common library usage pattern (issue #595): consumer enables
# std + arc + rvm and relies on indexmap/std propagation.
- name: library (std + arc + rvm)
features: std,arc,rvm
# New default after removing mimalloc from full-opa.
# Ensures all builtins compile without the allocator.
- name: full-opa (no mimalloc)
features: std,arc,full-opa
# Binding-style usage: full-opa with the vendored allocator.
# Mirrors how ffi/java/python/ruby bindings are built.
- name: full-opa + allocator
features: std,arc,full-opa,allocator-memory-limits
# Selective builtins without full-opa: validates that popular
# features can be cherry-picked independently.
- name: cherry-picked builtins
features: std,arc,rvm,regex,time,semver,cache
# Observability features only: coverage + cache without the
# heavier builtins (regex, time, etc.).
- name: observability
features: std,arc,rvm,coverage,cache
# Azure Policy adds jsonschema + dashmap; test it compiles
# and runs on top of full-opa.
- name: azure-policy
features: std,arc,full-opa,azure_policy
# Azure RBAC adds regex + time + net on top of full-opa.
- name: azure-rbac
features: std,arc,full-opa,azure-rbac
# no_std with the OPA-compatible feature set: exercises the
# spin_no_std codepath and absence of std-only dependencies.
- name: no_std
features: arc,opa-no-std
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus-features
- name: Fetch dependencies
run: cargo fetch --locked
- name: Build
run: cargo build --no-default-features --features "${{ matrix.features }}" --frozen
- name: Test
run: cargo test --no-default-features --features "${{ matrix.features }}" --frozen

View File

@@ -1,29 +0,0 @@
name: miri
on:
workflow_dispatch:
schedule:
# Run at 6:30 AM UTC every Wednesday
- cron: "30 6 * * 3"
jobs:
miri-test:
name: miri (nightly)
runs-on: ubuntu-latest
env:
MIRIFLAGS: "-Zmiri-disable-isolation"
steps:
- name: Checkout repository
uses: actions/checkout@v6
- uses: ./.github/actions/toolchains/rust
with:
toolchain: nightly
components: miri rust-src
- name: Set up Miri
run: cargo miri setup
- name: Run Miri tests
run: cargo miri test -p regorus
- name: Run Miri ACI tests
run: cargo miri test -p regorus --test aci
- name: Run Miri kata tests
run: cargo miri test -p regorus --test kata

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release-extensions
on:
@@ -7,9 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -20,18 +15,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run rego extensions CI suite
- uses: actions/checkout@v4
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std,rego-extensions"
- name: Doc Tests
run: cargo test -r --doc --features rego-extensions
- name: Run tests
run: cargo test -r --features rego-extensions
- name: Run example
run: cargo run --example regorus --features rego-extensions -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci --features rego-extensions
- name: Run tests (KATA)
run: cargo test -r --test kata --features rego-extensions
- name: Run tests (OPA Conformance)
run: >-
cargo xtask ci-release --frozen --features rego-extensions
--skip-all-features-build --skip-no-default-features-tests
--skip-azure-policy --skip-azure-rbac
--opa-features "opa-testutil,serde_json/arbitrary_precision,rego-extensions"
cargo test -r --test opa --features opa-testutil,serde_json/arbitrary_precision,rego-extensions -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/release
on:
@@ -7,9 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -20,14 +15,29 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run release CI suite
run: cargo xtask ci-release --frozen
- uses: actions/checkout@v4
- name: Format Check
run: cargo fmt --check
- name: Fetch
run: cargo fetch
- name: Build (all features)
run: cargo build -r --all-features --frozen
- name: Build
run: cargo build -r --frozen
- name: Test no_std
run: cargo test -r --no-default-features --frozen
- name: Build only std
run: cargo build -r --example regorus --no-default-features --features "std" --frozen
- name: Doc Tests
run: cargo test -r --doc --frozen
- name: Run tests
run: cargo test -r --frozen
- name: Run example
run: cargo run --example regorus --frozen -- eval -d examples/server/allowed_server.rego -i examples/server/input.json data.example
- name: Run tests (ACI)
run: cargo test -r --test aci --frozen
- name: Run tests (KATA)
run: cargo test -r --test kata --frozen
- name: Run tests (OPA Conformance)
run: >-
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -1,9 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-java
on:
workflow_dispatch:
on: workflow_dispatch
permissions:
contents: read
@@ -35,28 +32,27 @@ jobs:
os: windows-latest
extension: dll
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@v4
with:
java-version: 8
distribution: "corretto"
- uses: ./.github/actions/toolchains/rust
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- if: ${{ matrix.build_cmd == 'zigbuild' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
uses: actions/setup-python@v5
with:
python-version: "3.11"
- if: ${{ matrix.build_cmd == 'zigbuild' }}
run: pip install cargo-zigbuild
- run: cargo fetch --locked
- run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- run: cargo fetch
- run: cargo ${{ matrix.build_cmd || 'build' }} --release --frozen --target ${{ matrix.target }}${{ matrix.glibc && format('.{0}', matrix.glibc) || '' }} --manifest-path ./bindings/java/Cargo.toml
- run: mkdir -p native/${{ matrix.target }}
- run: mv target/${{ matrix.target }}/release/*.${{ matrix.extension }} ./native/${{ matrix.target }}/
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- uses: actions/upload-artifact@v4
with:
name: native-libraries-${{ matrix.target }}
path: native/
@@ -66,24 +62,24 @@ jobs:
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@v4
with:
java-version: 8
distribution: "corretto"
server-id: ossrh
server-username: MAVEN_USERNAME
server-password: MAVEN_PASSWORD
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- uses: actions/download-artifact@v4
with:
pattern: native-libraries-*
merge-multiple: true
path: ./bindings/java/native/
- run: mvn package
working-directory: ./bindings/java
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- uses: actions/upload-artifact@v4
with:
name: built-jars
path: ./bindings/java/target/regorus-java-*.jar

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This file is autogenerated by maturin v1.4.0
# To update, run
#
@@ -20,30 +18,29 @@ jobs:
matrix:
target: [x86_64, x86, aarch64, armv7, s390x, ppc64le]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch --locked
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.target }} --frozen
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@v3
with:
name: wheels-linux-${{ matrix.target }}
name: wheels
path: dist
windows:
@@ -52,30 +49,29 @@ jobs:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
architecture: ${{ matrix.target }}
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch --locked
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --frozen --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@v3
with:
name: wheels-windows-${{ matrix.target }}
name: wheels
path: dist
macos:
@@ -84,29 +80,28 @@ jobs:
matrix:
target: [x86_64, aarch64, universal2-apple-darwin]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- uses: ./.github/actions/toolchains/rust
- name: Build Python extension
run: |
cargo fetch --locked
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build wheels
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: ${{ matrix.target }}
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@v3
with:
name: wheels-macos-${{ matrix.host.target }}
name: wheels
path: dist
release:
@@ -114,15 +109,13 @@ jobs:
runs-on: ubuntu-latest
# Commented out for initial release.
# if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos]
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- uses: actions/download-artifact@v3
with:
pattern: wheels-*
merge-multiple: true
path: wheels
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@04ac600d27cdf7a9a280dadf7147097c42b757ad # v1.43.0
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:

View File

@@ -1,13 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: publish-wasm
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
on: workflow_dispatch
jobs:
publish-wasm:
@@ -15,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'

View File

@@ -1,29 +1,24 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: Release-plz
permissions:
pull-requests: write
contents: write
on:
workflow_dispatch:
on: workflow_dispatch
jobs:
release-plz:
name: Release-plz
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: ./.github/actions/toolchains/rust
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: MarcoIeni/release-plz-action@1528104d2ca23787631a1c1f022abb64b34c1e11 # v0.5.128
uses: MarcoIeni/release-plz-action@98b2b45b090aadf18cb662caaf3de6222d98822a #v0.5.60
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
@@ -18,9 +16,6 @@ on:
# The branches below must be a subset of the branches above
branches: [ "main" ]
workflow_dispatch:
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
rust-clippy-analyze:
@@ -32,27 +27,32 @@ jobs:
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Install Rust toolchain
uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1
with:
shared-key: ${{ runner.os }}-regorus
profile: minimal
toolchain: stable
components: clippy
override: true
- name: Install required cargo
run: cargo install clippy-sarif sarif-fmt
- name: Fetch
run: cargo fetch --locked
run: cargo fetch
- name: Run rust-clippy
run: cargo xtask clippy --sarif rust-clippy-results.sarif
run:
cargo clippy
--all-features
--message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt
--frozen
continue-on-error: true
- name: Upload analysis results to GitHub
if: ${{ hashFiles('rust-clippy-results.sarif') != '' }}
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.11
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: rust-clippy-results.sarif
wait-for-processing: true

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/c-cpp
on:
@@ -7,38 +5,45 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Setup gcc, g++, cmake, ninja
run: sudo apt update && sudo apt install -y gcc g++ cmake ninja-build
- name: Test C binding via xtask
run: cargo xtask test-c --release --frozen
- name: Workaround to ensure that regorus.h is generated
run: |
cargo fetch
cargo build -r --frozen
working-directory: ./bindings/ffi
- name: Test c binding
run: |
mkdir bindings/c/build
cd bindings/c/build
cmake -G Ninja ..
ninja
./regorus_test
- name: Test C (no-std) binding via xtask
run: cargo xtask test-c-nostd --release --frozen --skip-ffi
- name: Test c-nostd binding
run: |
mkdir bindings/c-nostd/build
cd bindings/c-nostd/build
cmake -G Ninja ..
ninja
./regorus_test
- name: Test C++ binding via xtask
run: cargo xtask test-cpp --release --frozen --skip-ffi
- name: Test cpp binding
run: |
mkdir bindings/cpp/build
cd bindings/cpp/build
cmake -G Ninja ..
ninja
./regorus_test

View File

@@ -1,19 +1,10 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/csharp
on:
workflow_dispatch:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
VersionSuffix: ${{ github.event_name == 'workflow_dispatch' && 'manualtrigger' || null }}
jobs:
build-ffi:
@@ -33,33 +24,23 @@ jobs:
target: x86_64-unknown-linux-gnu
libpath: |
**/release/libregorus_ffi.so
- os: macos-latest
target: aarch64-apple-darwin
libpath: |
**/release/libregorus_ffi.dylib
# Disabled for now
#- os: macos-latest
# target: aarch64-apple-darwin
# libpath: |
# **/release/libregorus_ffi.dylib
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.runtime.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml --target ${{ matrix.runtime.target }}
- name: Build Regorus FFI via xtask
run: cargo xtask build-ffi --release --target ${{ matrix.runtime.target }}
- name: Build Regorus binding
run: cargo build -r --target ${{ matrix.runtime.target }} --locked
working-directory: ./bindings/ffi
- name: Upload regorus ffi shared library
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@v4
with:
name: regorus-ffi-artifacts-${{ matrix.runtime.target }}
# Note: The full path of each artifact relative to . is preserved.
@@ -67,32 +48,23 @@ jobs:
if-no-files-found: error
retention-days: 1
build-csharp:
build-nuget:
name: 'Build Regorus nuget'
runs-on: ubuntu-latest
needs: build-ffi
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Download regorus ffi shared libraries
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@v4
with:
pattern: regorus-ffi-artifacts-*
merge-multiple: true
@@ -101,22 +73,26 @@ jobs:
- name: Display regorus ffi artifacts
run: ls -R ./bindings/csharp/Regorus/tmp
- name: Build Regorus nuget via xtask
run: cargo xtask build-csharp --release --clean --artifacts-dir ./bindings/csharp/Regorus/tmp/bindings/ffi/target --enforce-artifacts --repository-commit ${{ github.sha }} --include-symbols
# Note that we need to supply the target folder within the folder where artifacts are downloaded.
- name: Build Regorus binding
run: dotnet build /p:Configuration=Release /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
working-directory: ./bindings/csharp/Regorus
- name: Pack
run: dotnet pack /p:RegorusFFIArtifactsDir=./tmp/bindings/ffi/target
working-directory: ./bindings/csharp/Regorus
- name: Upload Regorus nuget
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@v4
with:
name: regorus-nuget
path: |
bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
bindings/csharp/Regorus/bin/Release/Regorus*.snupkg
path: bindings/csharp/Regorus/bin/Release/Regorus*.nupkg
if-no-files-found: error
retention-days: 1
test-nuget:
name: 'Test Regorus Nuget: (${{ matrix.runtime.target }})'
needs: build-csharp
needs: build-nuget
runs-on: ${{ matrix.runtime.os }}
strategy:
# let us get failures from other jobs even if one fails
@@ -127,40 +103,30 @@ jobs:
target: x86_64-pc-windows-msvc
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: aarch64-apple-darwin
#- os: macos-latest
# target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
- uses: actions/setup-dotnet@v4
with:
global-json-file: ./bindings/csharp/global.json
- run: echo '${{ steps.stepid.outputs.dotnet-version }}'
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Download regorus nuget
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: actions/download-artifact@v4
with:
name: regorus-nuget
path: ./bindings/csharp/Regorus/bin/Release
path: ./bindings/csharp/Regorus.Tests/regorus-nuget/
- name: Display regorus nuget
run: ls -R ./bindings/csharp/Regorus/bin/Release
- name: Restore Regorus.Tests
run: dotnet restore /p:RestoreAdditionalProjectSources=./regorus-nuget
working-directory: ./bindings/csharp/Regorus.Tests
- name: Run C# tests via xtask
run: cargo xtask test-csharp --release --clean --nuget-dir bindings/csharp/Regorus/bin/Release
- name: Run Regorus.Tests
run: dotnet test --no-restore
working-directory: ./bindings/csharp/Regorus.Tests

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ffi
on:
@@ -7,28 +5,19 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- name: Test FFI
run: cargo xtask test-ffi --release --frozen
run: |
cargo fetch
cargo build -r --frozen
cargo clippy --all-targets --no-deps -- -Dwarnings
working-directory: ./bindings/ffi

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/go
on:
@@ -7,32 +5,28 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch FFI crate dependencies
run: cargo fetch --locked --manifest-path bindings/ffi/Cargo.toml
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
- uses: actions/setup-go@v5
with:
architecture: x64
- name: Test Go binding via xtask
run: cargo xtask test-go --release --frozen
- name: Build ffi
run: cargo build -r
working-directory: ./bindings/ffi
- name: Test go
run: |
go mod tidy
go build
LD_LIBRARY_PATH=../ffi/target/release ./regorus_test
working-directory: ./bindings/go

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/java
on:
@@ -7,33 +5,33 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@v4
with:
java-version: 8
distribution: "corretto"
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- uses: dtolnay/rust-toolchain@stable
- name: Fetch Java crate dependencies
run: cargo fetch --locked --manifest-path bindings/java/Cargo.toml
- name: Building binding
run: |
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --manifest-path bindings/java/Cargo.toml --locked
- name: Run Java smoke tests via xtask
run: cargo xtask test-java --release --frozen
- name: Build jar
run: mvn package
working-directory: ./bindings/java
- name: Test jar
run: |
javac -cp target/regorus-java-0.2.2.jar Test.java
java -Djava.library.path=target/release -cp target/regorus-java-0.2.2.jar:. Test
working-directory: ./bindings/java

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: musl
on:
@@ -7,9 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -20,19 +15,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/toolchains/rust
with:
targets: x86_64-unknown-linux-musl
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch MUSL target dependencies
run: cargo fetch --locked --target x86_64-unknown-linux-musl
- uses: actions/checkout@v3
- name: Add musl target
run: rustup target add x86_64-unknown-linux-musl
- name: Install musl-gcc
run: sudo apt update && sudo apt install -y musl-tools
- name: Run MUSL suite via xtask
run: cargo xtask test-musl --release --frozen --target x86_64-unknown-linux-musl
- name: Fetch
run: cargo fetch
- name: Build (MUSL)
run: cargo build --verbose --all-targets --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL)
run: cargo test -r --verbose --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL ACI)
run: cargo test -r --test aci --target x86_64-unknown-linux-musl --frozen
- name: Run tests (KATA ACI)
run: cargo test -r --test kata --target x86_64-unknown-linux-musl --frozen
- name: Run tests (MUSL OPA Conformance)
run: >-
cargo test -r --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision --target x86_64-unknown-linux-musl -- $(tr '\n' ' ' < tests/opa.passing)

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/no-std
on:
@@ -7,9 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -20,18 +15,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ./.github/actions/toolchains/rust
with:
targets: thumbv7m-none-eabi
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch ensure_no_std crate dependencies
run: cargo fetch --locked --manifest-path tests/ensure_no_std/Cargo.toml --target thumbv7m-none-eabi
- name: Test no-std
run: cargo xtask test-no-std --release --frozen
- uses: actions/checkout@v3
- name: Add no_std target
run: rustup target add thumbv7m-none-eabi
- name: Fetch
run: cargo fetch
- name: Build
run: cargo build -r --target thumbv7m-none-eabi --frozen
working-directory: ./tests/ensure_no_std

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/python
on:
@@ -7,9 +5,9 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
PYTHON_VERSION: "3.10"
jobs:
build:
@@ -23,69 +21,69 @@ jobs:
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
with:
targets: ${{ matrix.host.target }}
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml --target ${{ matrix.host.target }}
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@v4
with:
python-version: "3.10"
python-version: ${{ env.PYTHON_VERSION }}
architecture: x64
- name: Install maturin
run: python -m pip install maturin==1.5.1
- name: Build Python extension
run: |
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
cargo build --release --target ${{ matrix.host.target }} --frozen
working-directory: bindings/python
- name: Build Python wheel via xtask
run: cargo xtask build-python --release --target ${{ matrix.host.target }} --target-dir bindings/python/dist --frozen
- name: Build Wheel
uses: PyO3/maturin-action@63b75c597b83e247fbf4fb7719801cc4220ae9f3 # v1.43.0
with:
target: x86_64
args: --release --out dist --manifest-path bindings/python/Cargo.toml --offline --strip
sccache: 'true'
- name: Upload wheel artefacts
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
- name: Upload Wheel
uses: actions/upload-artifact@v4
with:
name: regorus-wheel-${{ matrix.host.name }}
path: bindings/python/dist/regorus-*.whl
path: dist/regorus-*.whl
test:
needs: build
strategy:
matrix:
host: [ubuntu-24.04, ubuntu-22.04, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
runs-on: ${{ matrix.host }}
host:
- name: ubuntu-24.04
wheel: regorus-0.4.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: ubuntu-22.04
wheel: regorus-0.4.0-cp310-abi3-manylinux_2_34_x86_64.whl
- name: windows-latest
wheel: regorus-0.4.0-cp310-abi3-win_amd64.whl
needs: build
runs-on: ${{ matrix.host.name }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Download Regorus wheel
uses: actions/download-artifact@v4
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
path: wheels
pattern: regorus-wheel-*
merge-multiple: true
- name: Fetch Python crate dependencies
run: cargo fetch --locked --manifest-path bindings/python/Cargo.toml
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
architecture: x64
- name: Install maturin
run: python -m pip install maturin==1.5.1
- name: Run Python smoke tests via xtask
run: cargo xtask test-python --release --python python
- name: Test Wheel
run: |
pip3 install ../../wheels/${{ matrix.host.wheel }}
python3 test.py
working-directory: bindings/python

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/ruby
on:
@@ -10,16 +8,15 @@ on:
jobs:
test:
if: false # temporarily disabled
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Ruby and Rust
uses: oxidize-rb/actions/setup-ruby-and-rust@e5f9a49a7812a078584072f6e3f657ad247c8771 # v1.26
uses: oxidize-rb/actions/setup-ruby-and-rust@7ca44a16e287e5ff7dd72ab53f4bd41cbf34a571 #v1.26
with:
bundler: 2.6.5
rubygems: 3.6.5
@@ -29,16 +26,10 @@ jobs:
cargo-cache: true
working-directory: "bindings/ruby"
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch Ruby crate dependencies
run: cargo fetch --locked --manifest-path bindings/ruby/Cargo.toml
- name: Run ruby tests
run: cargo xtask test-ruby --release --frozen
run: |
cd bindings/ruby
gem install bundler
bundle install
cargo clippy --all-targets --no-deps -- -Dwarnings
bundle exec rake

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: bindings/wasm
on:
@@ -7,38 +5,31 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Fetch WASM crate dependencies
run: cargo fetch --locked --manifest-path bindings/wasm/Cargo.toml
- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@v4
with:
node-version: 22
node-version: 18
- name: Install wasmlpack
run: cargo install wasm-pack
- name: Test wasm binding via xtask
run: cargo xtask test-wasm --release --frozen --node node
- name: Test wasm binding
run: |
cd bindings/wasm
cargo fetch
cargo clippy --all-targets --no-deps -- -Dwarnings
wasm-pack build --target nodejs --release
# Enable when upstream issue is fixed.
# https://github.com/microsoft/regorus/issues/371
# wasm-pack test --release --node
node test.js

View File

@@ -1,5 +1,3 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
#
name: tests/debug
on:
@@ -7,9 +5,6 @@ on:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
# Run at 8:00 AM every day
- cron: "0 8 * * *"
env:
CARGO_TERM_COLOR: always
@@ -20,14 +15,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Rust toolchain
uses: ./.github/actions/toolchains/rust
- name: Cache cargo
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
shared-key: ${{ runner.os }}-regorus
- name: Fetch dependencies
run: cargo fetch --locked
- name: Run debug CI suite
run: cargo xtask ci-debug --frozen
- uses: actions/checkout@v4
- name: Fetch
run: cargo fetch
- name: Build (all features)
run: cargo build --all-features --frozen
- name: Build
run: cargo build --frozen
- name: Test no_std
run: cargo test --no-default-features --frozen
- name: Build only std
run: cargo build --example regorus --no-default-features --features "std" --frozen
- name: Doc Tests
run: cargo test --doc --frozen
- name: Run tests
run: cargo test --frozen
- name: Run tests (ACI)
run: cargo test --test aci --frozen
- name: Run tests (KATA)
run: cargo test --test kata --frozen
- name: Run tests (OPA Conformance)
run: >-
cargo test --test opa --frozen --features opa-testutil,serde_json/arbitrary_precision -- $(tr '\n' ' ' < tests/opa.passing)

31
.gitignore vendored
View File

@@ -25,35 +25,6 @@ bindings/ffi/regorus.ffi.hpp
bindings/*/target
# Temporary commit message files
.commit-msg.txt
# Local planning docs
docs/plans/
# C# build folders
**bin
**obj
bindings/csharp/.nuget/
# Bundler binstubs regenerated during ruby setup
bindings/ruby/bin/
# Visual Studio folders
**/*.vs
# Visual Studio solution files
*.sln
# JetBrains IDEs files
.idea/
# Java build artifacts
**/*.class
**/*.jar
bindings/java/.classpath
bindings/java/.project
bindings/java/.settings/
# Emacs temporary files
*~
**obj

View File

@@ -6,59 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Azure RBAC condition interpreter with builtin evaluation coverage and YAML test suite, including quantifier (ForAnyOfAnyValues/ForAllOfAllValues), datetime (DateTimeEquals), IP (IpInRange), GUID (GuidEquals), list (ListContains), and string (StringEquals) semantics.
- FFI surface for Azure RBAC condition evaluation (see bindings changelog for language-specific wrappers).
### Changed
- [**breaking**] Switch RVM binary serialization to postcard, bump the format to v4, and mark v1-3 loads as partial (recompile required).
## [0.9.1](https://github.com/microsoft/regorus/compare/regorus-v0.9.0...regorus-v0.9.1) - 2026-02-06
### Fixed
- Release native C# handles reliably to avoid memory growth ([#571](https://github.com/microsoft/regorus/pull/571)).
- Centralize C# handle gating with a short dispose wait and deferred release to avoid leaks while blocking new calls ([#571](https://github.com/microsoft/regorus/pull/571)).
### Added
- Manual C# memory growth tests for both `using` and finalizer paths ([#571](https://github.com/microsoft/regorus/pull/571)).
- C# test runner options for filtered tests, console logging, and skipping sample apps ([#571](https://github.com/microsoft/regorus/pull/571)).
## [0.5.0](https://github.com/microsoft/regorus/compare/regorus-v0.4.0...regorus-v0.5.0) - 2025-07-08
### Added
- [**breaking**] Indexes for nodes in the AST ([#414](https://github.com/anakrish/regorus/pull/414))
- Updates for Policy Framework ([#405](https://github.com/anakrish/regorus/pull/405))
- Regorus nuget package ([#383](https://github.com/anakrish/regorus/pull/383))
### Fixed
- emit import warning to stderr ([#430](https://github.com/anakrish/regorus/pull/430))
- Clippy warnings ([#424](https://github.com/anakrish/regorus/pull/424))
- Disallow else blocks for set rules ([#403](https://github.com/anakrish/regorus/pull/403))
- [**breaking**] Remove cryptographic builtins ([#396](https://github.com/anakrish/regorus/pull/396))
- [**breaking**] Fix glob.match behavior in presence of : ([#390](https://github.com/anakrish/regorus/pull/390))
- C# EvalRule ([#387](https://github.com/anakrish/regorus/pull/387))
### Other
- Update release-plz action to v0.5.108 ([#431](https://github.com/anakrish/regorus/pull/431))
- Early return for 'some in' statement ([#427](https://github.com/anakrish/regorus/pull/427))
- Support manually generating C# bindings via Github action and add a README ([#423](https://github.com/anakrish/regorus/pull/423))
- Make the bindings/cpp CMake project installable ([#416](https://github.com/anakrish/regorus/pull/416))
- *(deps)* bump clap from 4.5.38 to 4.5.39 ([#415](https://github.com/anakrish/regorus/pull/415))
- *(deps)* Update criterion and other deps ([#412](https://github.com/anakrish/regorus/pull/412))
- Basic benchmarking setup with Criterion ([#408](https://github.com/anakrish/regorus/pull/408))
- Default to Rego v1 in `regorus parse` ([#407](https://github.com/anakrish/regorus/pull/407))
- *(deps)* bump clap from 4.5.37 to 4.5.38 ([#406](https://github.com/anakrish/regorus/pull/406))
- Update dependencies ([#401](https://github.com/anakrish/regorus/pull/401))
- Add C# test examples ([#397](https://github.com/anakrish/regorus/pull/397))
- *(deps)* bump clap from 4.5.35 to 4.5.36 ([#395](https://github.com/anakrish/regorus/pull/395))
- Python binding portability ([#388](https://github.com/anakrish/regorus/pull/388))
- *(deps)* bump clap from 4.5.34 to 4.5.35 ([#389](https://github.com/anakrish/regorus/pull/389))
- Use VersionPrefix and VersionSuffix ([#385](https://github.com/anakrish/regorus/pull/385))
- Check-in Cargo.lock files and lockdown .net ([#384](https://github.com/anakrish/regorus/pull/384))
## [0.4.0](https://github.com/microsoft/regorus/compare/regorus-v0.3.0...regorus-v0.4.0) - 2025-03-14
### Fixed

1756
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,53 +2,42 @@
members = [
"tests/ensure_no_std",
"xtask",
]
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.9.1"
version = "0.4.0"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
license-file = "LICENSE"
repository = "https://github.com/microsoft/regorus"
keywords = ["interpreter", "no_std", "opa", "policy-as-code", "rego"]
# Enable verification with Verus
[package.metadata.verus]
verify = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
doctest = false
[features]
default = ["full-opa", "arc", "rvm"]
default = ["full-opa", "arc"]
arc = []
arc = ["scientific/arc"]
ast = []
azure_policy = ["dep:jsonschema", "dep:chrono", "dep:ipnet", "dep:icu_casemap", "dep:hashbrown", "arc", "dashmap"]
azure-rbac = ["regex", "time", "net"]
base64 = ["dep:data-encoding"]
base64url = ["dep:data-encoding"]
coverage = []
crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha2"]
deprecated = []
hex = ["dep:data-encoding"]
http = []
glob = ["dep:globset"]
graph = []
jsonschema = ["dep:jsonschema"]
mimalloc = ["dep:mimalloc"]
net = ["dep:ipnet"]
no_std = ["lazy_static/spin_no_std"]
opa-runtime = []
regex = ["dep:regex"]
cache = ["dep:lru"]
rvm = ["dep:postcard", "dep:indexmap"]
semver = ["dep:semver"]
allocator-memory-limits = ["std", "mimalloc", "mimalloc/allocator-memory-limits"]
std = ["rand/std", "rand/std_rng", "serde_json/std", "indexmap?/std", "msvc_spectre_libs", "dep:parking_lot" ]
std = ["rand/std", "rand/std_rng", "serde_json/std"]
time = ["dep:chrono", "dep:chrono-tz"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
@@ -57,15 +46,15 @@ full-opa = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"glob",
"graph",
"hex",
"http",
"jsonschema",
"net",
"opa-runtime",
"regex",
"cache",
"semver",
"std",
"time",
@@ -83,6 +72,8 @@ opa-no-std = [
"base64",
"base64url",
"coverage",
"crypto",
"deprecated",
"graph",
"hex",
"no_std",
@@ -101,58 +92,42 @@ opa-testutil = []
rand = ["dep:rand"]
[dependencies]
anyhow = { version = "1.0.102", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc", "alloc"] }
anyhow = { version = "1.0.45", default-features = false }
serde = {version = "1.0.150", default-features = false, features = ["derive", "rc"] }
serde_json = { version = "1.0.89", default-features = false, features = ["alloc"] }
hashbrown = { version = "0.16", default-features = false, features = ["default-hasher"], optional = true }
lazy_static = { version = "1.4.0", default-features = false }
thiserror = { version = "2.0", default-features = false }
# Crypto
constant_time_eq = {version = "0.4.0", optional = true, default-features = false }
hmac = {version = "0.12.1", optional = true, default-features = false}
sha2 = {version= "0.10.8", optional = true, default-features = false }
hex = {version = "0.4.3", optional = true, default-features = false, features = ["alloc"] }
md-5 = {version = "0.10.6", optional = true, default-features = false }
data-encoding = { version = "2.8.0", optional = true, default-features=false, features = ["alloc"] }
num-bigint = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }
parking_lot = { version = "0.12", optional = true }
spin = { version = "0.10.0", default-features = false, features = ["mutex", "spin_mutex"] }
scientific = { version = "0.5.3" }
globset = { version = "0.4.16", features = ["simd-accel"], default-features = false, optional = true }
regex = {version = "1.12.3", optional = true, default-features = false }
regex = {version = "1.11.1", optional = true, default-features = false }
semver = {version = "1.0.25", optional = true, default-features = false }
url = { version = "2.5.4", optional = true }
uuid = { version = "1.22.0", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.45.0", default-features = false, optional = true }
chrono = { version = "0.4.44", optional = true }
uuid = { version = "1.15.1", default-features = false, features = ["v4", "fast-rng"], optional = true }
jsonschema = { version = "0.29.0", default-features = false, optional = true }
chrono = { version = "0.4.40", optional = true }
chrono-tz = { version = "0.10.1", optional = true }
ipnet = { version = "2.12.0", optional = true, default-features = false }
icu_casemap = { version = "2.1", optional = true, default-features = false, features = ["compiled_data"] }
serde_yaml = {version = "0.9.16", default-features = false, optional = true }
# Specify thread_rng for in order to use random_range
rand = { version = "0.10.0", default-features = false, features = ["thread_rng"], optional = true }
# Causes the project to link with the Spectre-mitigated CRT and libs.
msvc_spectre_libs = { version = "0.1", features = ["error"], optional = true }
dashmap = { version = "6.1", default-features = false, optional = true }
lru = { version = "0.16", default-features = false, optional = true }
mimalloc = { package = "regorus-mimalloc", path = "mimalloc", version = "2.2.6", optional = true }
# rvm related deps
indexmap = { version = "2.12.1", default-features = false, features = ["serde"], optional = true }
postcard = { version = "1.1.3", default-features = false, features = ["alloc"], optional = true }
# Use Verus for verification
vstd = { version = "0.0.0-2026-03-17-2326" }
rand = { version = "0.9.0", default-features = false, features = ["thread_rng"], optional = true }
[dev-dependencies]
anyhow = "1.0.102"
anyhow = "1.0.45"
cfg-if = "1.0.0"
clap = { version = "4.5.53", features = ["derive"] }
prettydiff = { version = "0.9.0", default-features = false }
clap = { version = "4.5.36", features = ["derive"] }
prettydiff = { version = "0.8.0", default-features = false }
serde_yaml = "0.9.16"
test-generator = "0.3.1"
walkdir = "2.3.2"
criterion = { version = "0.8" }
num_cpus = "1.16"
[build-dependencies]
anyhow = "1.0"
@@ -178,39 +153,6 @@ name="kata"
harness=false
test=false
[[bench]]
name = "regorus_benchmark"
harness = false
[[bench]]
name = "schema_validation_benchmark"
harness = false
required-features = ["azure_policy"]
[[bench]]
name = "engine_evaluation_benchmark"
path = "benches/evaluation/engine_evaluation_benchmark.rs"
harness = false
[[bench]]
name = "compiled_policy_evaluation_benchmark"
path = "benches/evaluation/compiled_policy_evaluation_benchmark.rs"
harness = false
[[bench]]
name = "aci_benchmark"
harness = false
[[bench]]
name = "rvm_benchmark"
harness = false
required-features = ["rvm"]
[[bench]]
name = "normalization_benchmark"
harness = false
required-features = ["azure_policy"]
[[example]]
name="regorus"
harness=false
@@ -222,6 +164,3 @@ doctest=false
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(verus_keep_ghost)'] }

235
LICENSE
View File

@@ -19,238 +19,3 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
The file src/builtins/time/diff.rs contains code derived from Go's `time`
package, which carries the following license:
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Some files are licensed Apache-2.0 (LICENSE-2.0.txt).
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -3,7 +3,7 @@
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
Regorus is also
@@ -145,7 +145,6 @@ $ regorus
Usage: regorus <COMMAND>
Commands:
ast Parse a Rego policy and dump AST
eval Evaluate a Rego Query
lex Tokenize a Rego policy
parse Parse a Rego policy
@@ -274,19 +273,6 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso
Range (min … max): 43.8 ms … 46.7 ms 62 runs
```
## Contributor Workflow
Regorus uses a small companion CLI under the `xtask` package to keep CI and local development in sync.
The commands mirror our GitHub Actions jobs, making it easy to dry-run CI steps before sending a pull request.
- Run the full release pipeline with `cargo xtask ci-release` and the debug checks with `cargo xtask ci-debug`.
- Exercise language bindings through focused helpers such as `cargo xtask test-java --release --frozen` or `cargo xtask test-go`.
- Use `cargo xtask test-musl --release --frozen` for the cross-compilation matrix and `cargo xtask test-no-std` for embedded targets.
- Formatting (`cargo xtask fmt`) and linting (`cargo xtask clippy --sarif`) wrap the usual Cargo tooling while matching CI defaults.
The workflows in `.github/workflows` invoke the same commands, so keeping local runs green is usually enough to satisfy the checks enforced on `main`.
## OPA Conformance
Regorus has been verified to be compliant with [OPA v1.2.0](https://github.com/open-policy-agent/opa/releases/tag/v1.2.0)
@@ -302,6 +288,12 @@ Currently, Regorus passes all the non-builtin specific tests.
See [passing tests suites](https://github.com/microsoft/regorus/blob/main/tests/opa.passing).
The following test suites don't pass fully due to missing builtins:
- `cryptoparsersaprivatekeys`
- `cryptox509parseandverifycertificates`
- `cryptox509parsecertificaterequest`
- `cryptox509parsecertificates`
- `cryptox509parsekeypair`
- `cryptox509parsersaprivatekey`
- `globsmatch`
- `graphql`
- `invalidkeyerror`
@@ -316,8 +308,11 @@ The following test suites don't pass fully due to missing builtins:
- `jwtverifyhs384`
- `jwtverifyhs512`
- `jwtverifyrsa`
- `netcidrcontains`
- `netcidrcontainsmatches`
- `netcidrexpand`
- `netcidrintersects`
- `netcidrisvalid`
- `netcidrmerge`
- `netcidroverlap`
- `netlookupipaddr`
@@ -329,7 +324,7 @@ The following test suites don't pass fully due to missing builtins:
They are captured in the following [github issues](https://github.com/microsoft/regorus/issues?q=is%3Aopen+is%3Aissue+label%3Alib).
Cryptographic builtins are not supported by design. Users that need cryptographic builtins are encouraged to use [extensions](https://docs.rs/regorus/latest/regorus/struct.Engine.html#method.add_extension).
Cryptographically insecure `sha1` related builtins are intentionally not supported to discourage their use.
### Grammar

View File

@@ -1,80 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use std::path::Path;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct TestCase {
note: String,
data: Value,
input: Value,
modules: Vec<String>,
query: String,
want_result: Value,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct YamlTest {
cases: Vec<TestCase>,
}
fn aci_policy_eval(c: &mut Criterion) {
let dir = Path::new("tests/aci");
for entry in WalkDir::new(dir)
.sort_by_file_name()
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.to_string_lossy().ends_with(".yaml") {
continue;
}
let yaml = std::fs::read(path).expect("failed to read yaml test");
let yaml = String::from_utf8_lossy(&yaml);
let test: YamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml test");
for case in &test.cases {
let rule = case.query.replace("=x", "");
c.bench_with_input(
BenchmarkId::new("case ", format!("{} {}", &case.note, &rule)),
&case,
|b, case| {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_data(case.data.clone())
.expect("failed to add data");
engine.set_input(case.input.clone());
for (idx, rego) in case.modules.iter().enumerate() {
if rego.ends_with(".rego") {
let path = dir.join(rego);
let path = path.to_str().expect("not a valid path");
engine
.add_policy_from_file(path)
.expect("failed to add policy");
} else {
engine
.add_policy(format!("rego{idx}.rego"), rego.clone())
.expect("failed to add policy");
}
}
b.iter(|| {
engine.eval_rule(rule.clone()).unwrap();
})
},
);
}
}
}
criterion_group!(aci_benches, aci_policy_eval);
criterion_main!(aci_benches);

View File

@@ -1,157 +0,0 @@
# Regorus Multi-Threaded Evaluation Benchmark
A benchmark suite for measuring the multi-threaded performance of the Regorus policy evaluation engine.
## Overview
This benchmark evaluates the throughput and scalability of Regorus policy evaluation across different thread counts and configuration strategies. It measures performance variations between fresh and cloned engine instances, as well as fresh and cloned input data.
## Features
- **Multi-threaded evaluation** testing from 1 to `num_cpus * 2` threads
- **Configurable engine strategies**: Fresh vs. cloned engine instances
- **Configurable input strategies**: Fresh parsing vs. cloned input data
- **Complex policy evaluation** using realistic RBAC and data sensitivity policies
- **Criterion-based benchmarking** with statistical analysis
- **Performance metrics** including throughput and timing
## Benchmark Structure
### Test Configurations
The benchmark tests four different configuration combinations:
1. **Cloned Engines + Cloned Inputs**: Pre-instantiated engines with pre-parsed input data
2. **Cloned Engines + Fresh Inputs**: Pre-instantiated engines with fresh JSON parsing
3. **Fresh Engines + Cloned Inputs**: New engine instances with pre-parsed input data
4. **Fresh Engines + Fresh Inputs**: New engine instances with fresh JSON parsing
### Thread Scaling
Tests are performed with thread counts: 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32 (up to `num_cpus * 2`)
Each thread performs 1000 policy evaluations to ensure statistically significant measurements.
## Running the Benchmark
### Prerequisites
- Rust 1.70+
- Cargo
### Execution
Run the complete benchmark suite:
```bash
cargo bench evaluation_benchmark
```
Run specific benchmarks:
```bash
# Run only cloned engines with cloned inputs
cargo bench "cloned_engines , cloned_inputs"
# Run only single-threaded tests
cargo bench "1 threads"
```
### Output
Results are generated in the `target/criterion/` directory and include:
- Detailed timing statistics
- Throughput measurements (Kelem/s)
- Performance comparison with previous runs
- HTML reports with graphs and analysis
## Test Policies
The benchmark uses complex Rego policies that simulate real-world scenarios:
### RBAC Policy
- Role-based access control with hierarchical permissions
- User-role-resource mapping
- Action-based authorization
### Data Sensitivity Policy
- Multi-level data classification (public, internal, confidential, secret)
- Access level validation
- Clearance-based filtering
### Time-based Access Policy
- Business hours validation
- Temporal access control
- Schedule-based permissions
### Azure Resource Policies
- **VM Deployment**: VM size restrictions, regional compliance, security configurations
- **Storage Account Security**: Encryption requirements, network ACLs, HTTPS enforcement
- **Key Vault Access**: Service principal validation, soft delete requirements, conditional access
- **Network Security Groups**: Port restrictions, CIDR validation, priority-based rules
### Policy Complexity Features
- **Multi-condition validation**: Complex nested object property checks
- **Network operations**: CIDR matching and IP range validation
- **Time-based constraints**: Timestamp comparisons and business hour logic
- **Security compliance**: Encryption, authentication, and access control patterns
- **Azure Resource Manager**: Real-world cloud governance scenarios
## Configuration
### Benchmark Parameters
- **Evaluations per thread**: 1000
- **Measurement iterations**: 100 samples per configuration
- **Warm-up time**: 3 seconds
- **Measurement time**: 10 seconds (extended for high thread counts)
### Customization
The benchmark can be customized by modifying `evaluation_benchmark.rs`:
```rust
// Adjust evaluations per thread
let evals_per_thread = 1000;
// Modify thread count calculation
let max_threads = num_cpus::get() * 2;
// Configure test scenarios
let scenarios = [
(true, true), // cloned_engines, cloned_inputs
(true, false), // cloned_engines, fresh_inputs
(false, true), // fresh_engines, cloned_inputs
(false, false), // fresh_engines, fresh_inputs
];
```
## Understanding Results
### Metrics
- **Total Evaluation Time**: Total execution time for all evaluations across all threads (ms)
- **Throughput**: Evaluations per second measured in Kelem/s
- **Kelem/s**: Thousands of elements (policy evaluations) per second
- Example: 98.71 Kelem/s = 98,710 policy evaluations per second
### Interpretation
- **Lower time** = better performance
- **Higher throughput** = better performance
- **Consistent results** across runs indicate stable performance
- **Outliers** may indicate system interference or measurement variance
### Tips
- Run on dedicated hardware for consistent results
- Disable other applications during benchmarking
- Use release builds for accurate performance measurements
- Consider CPU affinity for highly controlled testing
## Files
- `evaluation_benchmark.rs`: Main benchmark implementation
- Results are saved to `../../target/criterion/` directory

View File

@@ -1,160 +0,0 @@
# Compiled Policy Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy and input data reuse strategies.
## Configuration Combinations
1. **Compiled Shared Policies, Cloned Inputs**: Each thread uses shared compiled policies and clones of parsed input data - optimal for performance
2. **Compiled Shared Policies, Fresh Inputs**: Each thread uses shared compiled policies but parses new inputs each time
3. **Compiled Per Iteration, Cloned Inputs**: Each thread compiles the policy each iteration but reuses input data
4. **Compiled Per Iteration, Fresh Inputs**: Each thread compiles new policies and parses new inputs for each iteration
## Performance Results
### Compiled Shared Policies, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.35 | 426 |
| 2 | 5.36 | 373 |
| 4 | 11.70 | 342 |
| 6 | 20.33 | 295 |
| 8 | 43.26 | 185 |
| 10 | 61.93 | 162 |
| 12 | 79.30 | 151 |
| 14 | 94.45 | 148 |
| 16 | 113.39 | 141 |
| 18 | 154.41 | 117 |
| 20 | 184.37 | 108 |
| 22 | 204.00 | 108 |
| 24 | 220.45 | 109 |
| 26 | 237.07 | 110 |
| 28 | 252.58 | 111 |
| 30 | 273.57 | 110 |
| 32 | 292.69 | 109 |
### Compiled Shared Policies, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.34 | 299 |
| 2 | 7.29 | 274 |
| 4 | 15.19 | 263 |
| 6 | 24.90 | 241 |
| 8 | 49.22 | 163 |
| 10 | 68.45 | 146 |
| 12 | 86.55 | 139 |
| 14 | 104.77 | 134 |
| 16 | 136.07 | 118 |
| 18 | 169.05 | 106 |
| 20 | 198.25 | 101 |
| 22 | 217.05 | 101 |
| 24 | 234.75 | 102 |
| 26 | 254.53 | 102 |
| 28 | 276.06 | 101 |
| 30 | 296.12 | 101 |
| 32 | 318.81 | 100 |
### Compiled Per Iteration, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.11 | 55 |
| 2 | 36.89 | 54 |
| 4 | 75.46 | 53 |
| 6 | 114.66 | 52 |
| 8 | 152.80 | 52 |
| 10 | 192.17 | 52 |
| 12 | 232.32 | 52 |
| 14 | 301.47 | 46 |
| 16 | 380.36 | 42 |
| 18 | 424.64 | 42 |
| 20 | 484.76 | 41 |
| 22 | 531.62 | 41 |
| 24 | 582.88 | 41 |
| 26 | 631.39 | 41 |
| 28 | 671.99 | 42 |
| 30 | 717.65 | 42 |
| 32 | 766.05 | 42 |
### Compiled Per Iteration, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 19.07 | 52 |
| 2 | 38.89 | 51 |
| 4 | 79.52 | 50 |
| 6 | 120.89 | 50 |
| 8 | 161.08 | 50 |
| 10 | 202.37 | 49 |
| 12 | 244.04 | 49 |
| 14 | 316.66 | 44 |
| 16 | 398.02 | 40 |
| 18 | 449.54 | 40 |
| 20 | 500.57 | 40 |
| 22 | 557.97 | 39 |
| 24 | 605.71 | 40 |
| 26 | 656.88 | 40 |
| 28 | 710.03 | 39 |
| 30 | 741.09 | 40 |
| 32 | 801.26 | 40 |
## Analysis
The compiled policy benchmark demonstrates the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Compiled shared policies with cloned inputs provide the highest throughput
2. **Compilation Impact**:
- Pre-compiled policies: Significantly faster than per-iteration compilation
- Per-iteration compilation: Major overhead (~7-8x slower than pre-compiled)
3. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policy configurations
- mimalloc provides better thread scaling characteristics compared to the default allocator
- Higher thread counts show performance degradation due to contention, but less severe with mimalloc
- Per-iteration compilation shows poor scaling across all thread counts
4. **Input Processing**: Fresh inputs add ~30% overhead across all configurations
5. **Thread Performance with mimalloc**:
- Peak performance at 1 thread for most configurations
- Reasonable performance maintained up to 12-16 threads for shared policies
- Compiled policies show better thread scaling than per-iteration compilation
- mimalloc helps reduce allocation-related contention in multi-threaded scenarios
## Comparison with Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared/Cloned | 426 / 423 | 342 / 406 | 185 / 341 |
| Shared/Fresh | 299 / 309 | 263 / 297 | 163 / 266 |
| Per-iteration/Cloned | 55 / 56 | 53 / 54 | 52 / 53 |
| Per-iteration/Fresh | 52 / 53 | 50 / 51 | 50 / 51 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared/Cloned | 384 / 414 | 203 / 329 | 123 / 250 |
| Shared/Fresh | 284 / 302 | 176 / 235 | 108 / 201 |
| Per-iteration/Cloned | 54 / 55 | 50 / 52 | 42 / 42 |
| Per-iteration/Fresh | 51 / 52 | 47 / 50 | 40 / 40 |
The compiled policy evaluation shows performance characteristics that are generally comparable to engine evaluation, though with some notable differences. While single-threaded performance is very close between the systems, there are observable impacts from the compilation approach that become more apparent under different threading scenarios.
**Key Observations:**
- **Single-threaded performance**: Very close parity between systems, though results may vary between runs
- **Threading behavior**: Engine evaluation demonstrates better scaling characteristics under higher thread contention (4+ threads)
- **Multi-threaded impact**: Compiled policies show more pronounced performance degradation under thread contention in shared policy configurations
- **Contention resistance**: Per-iteration compilation shows more consistent (though lower absolute) performance across thread counts
- **Optimal usage**: Both systems achieve best results with minimal threading (1-4 threads), though engine evaluation maintains better performance at higher thread counts for shared configurations

View File

@@ -1,232 +0,0 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{compile_policy_with_entrypoint, CompiledPolicy, PolicyModule, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_compiled_eval(
num_threads: usize,
evals_per_thread: usize,
use_shared_policies: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Pre-compile all policies and share them between threads (only if using shared policies)
let compiled_policies: Option<Arc<Vec<CompiledPolicy>>> = if use_shared_policies {
Some(Arc::new(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap()
})
.collect(),
))
} else {
None
};
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let total_evals = Arc::new(Mutex::new(0usize));
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let compiled_policies = compiled_policies.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (_, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
Value::from_json_str(input).unwrap()
};
let result = if let Some(ref compiled_policies_vec) = compiled_policies {
// Use pre-compiled policy
let compiled_policy = &compiled_policies_vec[policy_idx];
compiled_policy.eval_with_input(input_value)
} else {
// Compile policy in each iteration
let (policy, _) = &policies_with_inputs[policy_idx];
let module = PolicyModule {
id: "policy.rego".into(),
content: policy.as_str().into(),
};
let compiled_policy = compile_policy_with_entrypoint(
Value::new_object(),
&[module],
"data.bench.allow".into(),
)
.unwrap();
compiled_policy.eval_with_input(input_value)
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!(
"Running compiled policy benchmark with max_threads: {}",
max_threads
);
let evals_per_thread = 1000;
// Benchmark all combinations of compilation strategy and input strategy
for use_shared_policies in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_shared_policies, use_cloned_inputs) {
(true, true) => "compiled_shared_policies, cloned_inputs ",
(true, false) => "compiled_shared_policies, fresh_inputs ",
(false, true) => "compiled_per_iteration , cloned_inputs ",
(false, false) => "compiled_per_iteration , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("compiled_eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_compiled_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_shared_policies),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nCompiled Policy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,127 +0,0 @@
# Engine Evaluation Benchmark Results
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **Rust Version**: 1.82.0
- **Allocator**: mimalloc (default allocator)
- **Benchmark Framework**: Criterion.rs
- **Test Data**: 20,000 inputs per evaluation (1000 per thread)
- **Policy**: Complex authorization policy with nested rules
## Benchmark Overview
The engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine and input data reuse strategies.
## Configuration Combinations
1. **Cloned Engines, Cloned Inputs**: Each thread uses its own engine and clones of parsed input data - optimal for performance
2. **Cloned Engines, Fresh Inputs**: Each thread uses its own engine but parses new inputs each time
3. **Fresh Engines, Cloned Inputs**: Each thread creates a new engine each iteration but reuses input data
4. **Fresh Engines, Fresh Inputs**: Each thread creates new engines and parses new inputs for each iteration
## Performance Results
### Cloned Engines, Cloned Inputs (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2.36 | 423 |
| 2 | 4.85 | 412 |
| 4 | 9.86 | 406 |
| 6 | 15.02 | 399 |
| 8 | 23.46 | 341 |
| 10 | 33.34 | 300 |
| 12 | 40.69 | 295 |
| 14 | 48.26 | 290 |
| 16 | 58.61 | 273 |
| 18 | 77.35 | 233 |
| 20 | 86.74 | 231 |
| 22 | 94.17 | 234 |
| 24 | 102.58 | 234 |
| 26 | 110.17 | 236 |
| 28 | 118.97 | 235 |
| 30 | 126.54 | 237 |
| 32 | 135.89 | 235 |
### Cloned Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 3.24 | 309 |
| 2 | 6.57 | 304 |
| 4 | 13.47 | 297 |
| 6 | 20.42 | 294 |
| 8 | 30.01 | 266 |
| 10 | 40.99 | 244 |
| 12 | 49.99 | 240 |
| 14 | 60.09 | 233 |
| 16 | 73.95 | 216 |
| 18 | 95.94 | 188 |
| 20 | 105.24 | 190 |
| 22 | 114.30 | 192 |
| 24 | 124.67 | 193 |
| 26 | 134.76 | 193 |
| 28 | 145.16 | 193 |
| 30 | 155.23 | 193 |
| 32 | 165.42 | 193 |
### Fresh Engines, Cloned Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 17.88 | 56 |
| 2 | 36.32 | 55 |
| 4 | 74.45 | 54 |
| 6 | 112.95 | 53 |
| 8 | 150.24 | 53 |
| 10 | 189.61 | 53 |
| 12 | 228.25 | 53 |
| 14 | 297.37 | 47 |
| 16 | 373.61 | 43 |
| 18 | 426.46 | 42 |
| 20 | 477.80 | 42 |
| 22 | 523.00 | 42 |
| 24 | 570.74 | 42 |
| 26 | 619.92 | 42 |
| 28 | 670.24 | 42 |
| 30 | 717.47 | 42 |
| 32 | 748.25 | 43 |
### Fresh Engines, Fresh Inputs
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 18.69 | 53 |
| 2 | 38.03 | 53 |
| 4 | 77.82 | 51 |
| 6 | 118.30 | 51 |
| 8 | 157.65 | 51 |
| 10 | 197.97 | 51 |
| 12 | 239.05 | 50 |
| 14 | 310.06 | 45 |
| 16 | 391.36 | 41 |
| 18 | 441.63 | 41 |
| 20 | 495.88 | 40 |
| 22 | 543.69 | 40 |
| 24 | 591.51 | 41 |
| 26 | 645.98 | 40 |
| 28 | 697.37 | 40 |
| 30 | 749.37 | 40 |
| 32 | 784.63 | 41 |
## Analysis
The benchmark results demonstrate the following performance characteristics with mimalloc as the default allocator:
1. **Best Performance**: Cloned engines with cloned inputs consistently deliver the highest throughput
2. **Configuration Performance Hierarchy**:
- Cloned engines, cloned inputs: Best performance (optimal configuration)
- Cloned engines, fresh inputs: ~27% reduction from optimal
- Fresh engines, cloned inputs: ~87% reduction from optimal
- Fresh engines, fresh inputs: ~87% reduction from optimal
3. **Scaling Patterns with mimalloc**:
- Performance degrades with increased thread count due to contention, but mimalloc provides better thread scaling characteristics
- Best throughput achieved at 1 thread for cloned engine configurations
- Fresh engine configurations show poor scaling across all thread counts
- The use of mimalloc as the default allocator has improved multi-threaded performance and reduced contention
4. **Engine Creation Overhead**: Fresh engine creation is a significant performance bottleneck (~7-8x slower than cloned engines)
5. **Input Processing**: Fresh input generation adds moderate overhead (~27% impact compared to cloned inputs)
6. **Thread Contention**: Performance degradation occurs with higher thread counts across all configurations, though mimalloc helps mitigate some allocation-related contention

View File

@@ -1,228 +0,0 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use regorus::{Engine, Value};
use std::collections::HashMap;
use std::hint::black_box;
use std::sync::{Arc, Barrier, Mutex};
use std::thread;
use std::time::Duration;
mod policy_data;
fn multi_threaded_eval(
num_threads: usize,
evals_per_thread: usize,
use_cloned_engines: bool,
use_cloned_inputs: bool,
) -> (std::time::Duration, HashMap<String, usize>, usize) {
// Complex policies with multiple valid inputs for each
let policies_with_inputs = policy_data::policies_with_inputs();
// Policy names for tracking
let policy_names = policy_data::policy_names()
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
// Initialize policy evaluation counters
let policy_counters = Arc::new(Mutex::new(HashMap::new()));
for policy_name in &policy_names {
policy_counters
.lock()
.unwrap()
.insert(policy_name.to_string(), 0);
}
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = Vec::with_capacity(num_threads);
let total_evals = Arc::new(Mutex::new(0usize));
for thread_id in 0..num_threads {
let barrier = barrier.clone();
let policies_with_inputs = policies_with_inputs.clone();
let policy_names = policy_names.clone();
let policy_counters = policy_counters.clone();
let total_evals = total_evals.clone();
handles.push(thread::spawn(move || {
let mut elapsed = std::time::Duration::ZERO;
// Pre-create engines if using cloned engines
let engines = if use_cloned_engines {
Some(
policies_with_inputs
.iter()
.map(|(policy, _)| {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
{
// Warm up the engine to ensure it's fully prepared for evaluation.
// This prevents each cloned engine from repeating preparation work.
engine.set_input(Value::new_object());
let _ = engine.eval_rule("data.bench.allow".to_string());
}
engine
})
.collect::<Vec<_>>(),
)
} else {
None
};
// Pre-parse inputs if using cloned inputs
let parsed_inputs = if use_cloned_inputs {
Some(
policies_with_inputs
.iter()
.map(|(_, inputs)| {
inputs
.iter()
.map(|input_str| regorus::Value::from_json_str(input_str).unwrap())
.collect::<Vec<_>>()
})
.collect::<Vec<_>>(),
)
} else {
None
};
barrier.wait();
for i in 0..evals_per_thread {
// Use different policy for each iteration - thread_id ensures different threads
// start with different policies for better load distribution
let policy_idx = (thread_id + i) % policies_with_inputs.len();
let (policy, inputs) = &policies_with_inputs[policy_idx];
// Use different input for the same policy based on iteration - thread_id ensures
// different threads start with different inputs for better load distribution
let input_idx = (thread_id + i) % inputs.len();
let input = &inputs[input_idx];
let start = std::time::Instant::now();
let result = {
let mut engine = if use_cloned_engines {
engines.as_ref().unwrap()[policy_idx].clone()
} else {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
};
let input_value = if use_cloned_inputs {
parsed_inputs.as_ref().unwrap()[policy_idx][input_idx].clone()
} else {
regorus::Value::from_json_str(input).unwrap()
};
engine.set_input(input_value);
engine.eval_rule("data.bench.allow".to_string())
// Engine cleanup/drop time is included in measurement to reflect
// real-world total cost of policy evaluation lifecycle
};
elapsed += start.elapsed();
// Track total and successful evaluations
{
let mut total = total_evals.lock().unwrap();
*total += 1;
}
if result.is_ok() {
if let Some(policy_name) = policy_names.get(policy_idx) {
let mut counters = policy_counters.lock().unwrap();
*counters.entry(policy_name.to_string()).or_insert(0) += 1;
}
}
}
elapsed
}));
}
let mut total = std::time::Duration::ZERO;
for handle in handles {
total += handle.join().unwrap();
}
let final_counters = policy_counters.lock().unwrap().clone();
let total_evals = *total_evals.lock().unwrap();
(total, final_counters, total_evals)
}
fn criterion_benchmark(c: &mut Criterion) {
let max_threads = num_cpus::get() * 2;
println!("Running benchmark with max_threads: {}", max_threads);
let evals_per_thread = 1000;
// Benchmark all combinations of cloned engines and inputs
for use_cloned_engines in [true, false] {
for use_cloned_inputs in [true, false] {
let group_name = match (use_cloned_engines, use_cloned_inputs) {
(true, true) => "cloned_engines , cloned_inputs ",
(true, false) => "cloned_engines , fresh_inputs ",
(false, true) => "fresh_engines , cloned_inputs ",
(false, false) => "fresh_engines , fresh_inputs ",
};
let mut group = c.benchmark_group(group_name);
group.measurement_time(Duration::from_secs(5));
// Test specific thread counts: powers of 2 + some intermediate values
let thread_counts: Vec<usize> = (1..=max_threads)
.filter(|&n| {
n == 1 || // Always test single-threaded
n % 2 == 0 || // Always test even threads
n == max_threads // Maximum threads
})
.collect();
for threads in thread_counts {
let total_evals = threads * evals_per_thread;
group.throughput(Throughput::Elements(total_evals as u64));
group.bench_with_input(
BenchmarkId::new("eval", format!(" {threads} threads")),
&threads,
|b, &threads| {
b.iter_custom(|iters| {
let evals_per_thread = evals_per_thread * (iters as usize);
let (duration, policy_counters, total_evals_aggregated) = multi_threaded_eval(
black_box(threads),
black_box(evals_per_thread),
black_box(use_cloned_engines),
black_box(use_cloned_inputs),
);
// Sanity check: Ensure the expected number of evaluations matches the actual number performed per iteration batch.
// total_evals is the expected number for this batch, total_evals_aggregated is the sum over all iters.
assert_eq!(total_evals, total_evals_aggregated/iters as usize);
// On one iteration, print policy evaluation statistics
if iters == 1 {
// println!("\nPolicy Evaluation Statistics:");
for (policy_name, count) in &policy_counters {
// println!(" {}: {} evaluations", policy_name, count);
if *count == 0 {
println!("\x1b[31mERROR: Policy '{}' was never evaluated successfully!\x1b[0m", policy_name);
}
}
}
duration
});
},
);
}
group.finish();
}
}
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@@ -1,117 +0,0 @@
// This module provides the full set of policies, inputs, and policy names for evaluation benchmarks.
// Policies and inputs are now loaded from external files.
use std::fs;
use std::path::Path;
pub fn policies_with_inputs() -> Vec<(String, Vec<String>)> {
let policy_with_input_files = [
(
"rbac_policy.rego",
vec!["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
),
(
"api_access_policy.rego",
vec![
"api_access_input.json",
"api_access_input2.json",
"api_access_input3.json",
],
),
(
"data_sensitivity_policy.rego",
vec![
"data_sensitivity_input.json",
"data_sensitivity_input2.json",
"data_sensitivity_input3.json",
],
),
(
"time_based_policy.rego",
vec![
"time_based_input.json",
"time_based_input2.json",
"time_based_input3.json",
],
),
(
"data_processing_policy.rego",
vec![
"data_processing_input.json",
"data_processing_input2.json",
"data_processing_input3.json",
],
),
(
"azure_vm_policy.rego",
vec![
"azure_vm_input.json",
"azure_vm_input2.json",
"azure_vm_input3.json",
],
),
(
"azure_storage_policy.rego",
vec![
"azure_storage_input.json",
"azure_storage_input2.json",
"azure_storage_input3.json",
],
),
(
"azure_keyvault_policy.rego",
vec![
"azure_keyvault_input.json",
"azure_keyvault_input2.json",
"azure_keyvault_input3.json",
],
),
(
"azure_nsg_policy.rego",
vec![
"azure_nsg_input.json",
"azure_nsg_input2.json",
"azure_nsg_input3.json",
],
),
];
let mut policies_and_inputs = Vec::new();
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
for (policy_file, input_files) in policy_with_input_files.iter() {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read policy file {:?}: {}", policy_path, e));
let mut input_contents = Vec::new();
for input_file in input_files {
let input_path = base_dir.join("inputs").join(input_file);
let input_content = fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read input file {:?}: {}", input_path, e));
input_contents.push(input_content);
}
policies_and_inputs.push((policy_content, input_contents));
}
policies_and_inputs
}
pub fn policy_names() -> Vec<&'static str> {
vec![
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy",
]
}

View File

@@ -1,15 +0,0 @@
{
"request": {
"method": "GET",
"path": "/api/v1/users/123"
},
"user": {
"id": "user123",
"scope": ["read:users", "write:users"],
"department": "engineering"
},
"resource": {
"owner": "user123",
"type": "user"
}
}

View File

@@ -1,15 +0,0 @@
{
"request": {
"method": "POST",
"path": "/api/v1/users"
},
"user": {
"id": "user456",
"scope": ["write:users", "admin:users"],
"department": "engineering"
},
"resource": {
"owner": "user456",
"type": "user"
}
}

View File

@@ -1,15 +0,0 @@
{
"request": {
"method": "DELETE",
"path": "/api/v1/users/789"
},
"user": {
"id": "admin123",
"scope": ["admin:users"],
"department": "security"
},
"resource": {
"owner": "user789",
"type": "user"
}
}

View File

@@ -1,16 +0,0 @@
{
"vault": {
"name": "mykeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -1,16 +0,0 @@
{
"vault": {
"name": "devkeyvault",
"location": "westus2",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 30,
"enablePurgeProtection": false,
"networkAcls": {
"defaultAction": "Allow",
"bypass": "AzureServices"
},
"tags": {
"environment": "development"
}
}
}

View File

@@ -1,16 +0,0 @@
{
"vault": {
"name": "prodkeyvault",
"location": "eastus",
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enablePurgeProtection": true,
"networkAcls": {
"defaultAction": "Deny",
"bypass": "AzureServices"
},
"tags": {
"environment": "production"
}
}
}

View File

@@ -1,13 +0,0 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "10.0.0.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "80",
"priority": 1001
}
}

View File

@@ -1,13 +0,0 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "172.16.0.0/16",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "22",
"priority": 1200
}
}

View File

@@ -1,13 +0,0 @@
{
"operation": "Microsoft.Network/networkSecurityGroups/securityRules/write",
"rule": {
"direction": "Inbound",
"access": "Allow",
"protocol": "TCP",
"sourceAddressPrefix": "203.0.113.0/24",
"sourcePortRange": "*",
"destinationAddressPrefix": "*",
"destinationPortRange": "443",
"priority": 300
}
}

View File

@@ -1,15 +0,0 @@
{
"account": {
"name": "mystorageaccount",
"tier": "Standard",
"replication": "LRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "data",
"publicAccess": "None"
}
}

View File

@@ -1,15 +0,0 @@
{
"account": {
"name": "devstorageaccount",
"tier": "Premium",
"replication": "LRS",
"location": "westus2",
"tags": {
"environment": "production"
}
},
"container": {
"name": "logs",
"publicAccess": "None"
}
}

View File

@@ -1,15 +0,0 @@
{
"account": {
"name": "prodstorageaccount",
"tier": "Standard",
"replication": "GRS",
"location": "eastus",
"tags": {
"environment": "production"
}
},
"container": {
"name": "backups",
"publicAccess": "None"
}
}

View File

@@ -1,14 +0,0 @@
{
"vm": {
"size": "Standard_D2s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -1,14 +0,0 @@
{
"vm": {
"size": "Standard_B1s",
"os": "Windows",
"location": "westus2",
"tags": {
"environment": "dev",
"department": "marketing"
}
},
"user": {
"department": "marketing"
}
}

View File

@@ -1,14 +0,0 @@
{
"vm": {
"size": "Standard_D4s_v3",
"os": "Linux",
"location": "eastus",
"tags": {
"environment": "production",
"department": "engineering"
}
},
"user": {
"department": "engineering"
}
}

View File

@@ -1,16 +0,0 @@
{
"operation": "collect",
"data": {
"type": "email",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "marketing",
"date": "2023-01-15"
},
"user": {
"age": 25,
"location": "US"
}
}

View File

@@ -1,16 +0,0 @@
{
"operation": "process",
"data": {
"type": "survey_response",
"source": "user_input"
},
"consent": {
"given": true,
"purpose": "analytics",
"date": "2023-06-15"
},
"user": {
"age": 30,
"location": "US"
}
}

View File

@@ -1,16 +0,0 @@
{
"operation": "delete",
"data": {
"type": "user_profile",
"source": "database"
},
"consent": {
"given": false,
"purpose": "none",
"date": "2022-01-01"
},
"user": {
"age": 16,
"location": "EU"
}
}

View File

@@ -1,13 +0,0 @@
{
"data": {
"type": "user_profile",
"classification": "personal",
"contains_pii": true,
"region": "EU"
},
"user": {
"clearance": "confidential",
"location": "EU"
},
"operation": "read"
}

View File

@@ -1,13 +0,0 @@
{
"data": {
"type": "financial_report",
"classification": "confidential",
"contains_pii": false,
"region": "US"
},
"user": {
"clearance": "secret",
"location": "US"
},
"operation": "read"
}

View File

@@ -1,13 +0,0 @@
{
"data": {
"type": "public_announcement",
"classification": "public",
"contains_pii": false,
"region": "GLOBAL"
},
"user": {
"clearance": "public",
"location": "EU"
},
"operation": "read"
}

View File

@@ -1,12 +0,0 @@
{
"user": {
"name": "alice",
"roles": ["viewer", "editor"]
},
"resource": {
"name": "document1",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -1,12 +0,0 @@
{
"user": {
"name": "bob",
"roles": ["admin"]
},
"resource": {
"name": "document2",
"type": "document",
"owner": "bob"
},
"action": "write"
}

View File

@@ -1,12 +0,0 @@
{
"user": {
"name": "charlie",
"roles": ["viewer"]
},
"resource": {
"name": "document3",
"type": "document",
"owner": "alice"
},
"action": "read"
}

View File

@@ -1,11 +0,0 @@
{
"time": "09:30:00",
"day": "monday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -1,11 +0,0 @@
{
"time": "14:30:00",
"day": "wednesday",
"user": {
"role": "employee",
"shift": "day"
},
"request": {
"urgent": false
}
}

View File

@@ -1,11 +0,0 @@
{
"time": "22:00:00",
"day": "friday",
"user": {
"role": "admin",
"shift": "night"
},
"request": {
"urgent": true
}
}

View File

@@ -1,13 +0,0 @@
package bench
default allow := false
valid_api_paths := ["/api/v1/", "/api/v2/", "/api/v3/"]
allow if {
input.request.method == "GET"
some path in valid_api_paths
startswith(input.request.path, path)
input.user.authenticated == true
time.now_ns() - input.user.login_time < 86400000000000 # 24 hours in nanoseconds
}

View File

@@ -1,28 +0,0 @@
package bench
default allow := false
# Azure Key Vault access policy
valid_operations := [
"Microsoft.KeyVault/vaults/keys/read",
"Microsoft.KeyVault/vaults/secrets/read",
"Microsoft.KeyVault/vaults/certificates/read"
]
vault_admins := ["admin@company.com", "security@company.com"]
allow if {
input.operation in valid_operations
input.principal.type == "ServicePrincipal"
input.principal.appId != ""
input.resource.properties.enableSoftDelete == true
input.resource.properties.enablePurgeProtection == true
time.now_ns() - input.principal.createdTime < 31536000000000000 # Less than 1 year old
}
allow if {
input.operation in valid_operations
input.principal.type == "User"
input.principal.userPrincipalName in vault_admins
input.context.conditionalAccess.compliant == true
}

View File

@@ -1,31 +0,0 @@
package bench
default allow := false
# Azure Network Security Group rules policy
dangerous_ports := [22, 3389, 1433, 3306, 5432, 6379, 27017]
internal_networks := ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
is_internal_source if {
some network in internal_networks
net.cidr_contains(network, input.rule.sourceAddressPrefix)
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange != "*"
not input.rule.destinationPortRange in dangerous_ports
input.rule.sourceAddressPrefix != "*"
input.rule.sourceAddressPrefix != "Internet"
}
allow if {
input.operation == "Microsoft.Network/networkSecurityGroups/securityRules/write"
input.rule.direction == "Inbound"
input.rule.access == "Allow"
input.rule.destinationPortRange in dangerous_ports
is_internal_source
input.rule.priority >= 1000
}

View File

@@ -1,17 +0,0 @@
package bench
default allow := false
# Azure Storage Account security policy
required_encryption_algorithms := ["AES256", "RSA-OAEP"]
allow if {
input.operation == "Microsoft.Storage/storageAccounts/write"
input.resource.properties.supportsHttpsTrafficOnly == true
input.resource.properties.minimumTlsVersion == "TLS1_2"
input.resource.properties.encryption.services.blob.enabled == true
input.resource.properties.encryption.keySource == "Microsoft.Storage"
input.resource.properties.allowBlobPublicAccess == false
input.resource.properties.networkAcls.defaultAction == "Deny"
count(input.resource.properties.networkAcls.ipRules) > 0
}

View File

@@ -1,20 +0,0 @@
package bench
default allow := false
# Azure VM deployment policy
allowed_vm_sizes := [
"Standard_B1s", "Standard_B2s", "Standard_B4ms",
"Standard_D2s_v3", "Standard_D4s_v3", "Standard_F2s_v2"
]
allowed_regions := ["eastus", "westus2", "northeurope", "southeastasia"]
allow if {
input.operation == "Microsoft.Compute/virtualMachines/write"
input.resource.properties.hardwareProfile.vmSize in allowed_vm_sizes
input.resource.location in allowed_regions
input.resource.properties.osProfile.adminPassword == null # Require SSH keys
count(input.resource.tags) > 0 # Must have tags
input.resource.tags.environment in ["dev", "test", "prod"]
}

View File

@@ -1,28 +0,0 @@
package bench
default allow := false
# Complex data filtering and aggregation
sensitive_fields := ["ssn", "credit_card", "password"]
contains_sensitive_data if {
some field in sensitive_fields
object.get(input.data, field, null) != null
}
user_clearance_level := object.get(input.user.attributes, "clearance", 0)
required_clearance := 3 if contains_sensitive_data else := 1
allow if {
user_clearance_level >= required_clearance
input.operation in ["read", "export"]
count(input.data) > 0
count(input.data) <= 1000 # Limit data size
}
allow if {
input.user.role == "data_processor"
input.operation == "transform"
not contains_sensitive_data
}

View File

@@ -1,25 +0,0 @@
package bench
default allow := false
rbac_roles := {
"admin": ["read", "write", "delete", "admin"],
"manager": ["read", "write"],
"user": ["read"]
}
user_permissions contains perm if {
some role in input.user.roles
perm := rbac_roles[role][_]
}
allow if {
input.action in user_permissions
input.resource.owner == input.user.id
}
allow if {
input.action in user_permissions
input.resource.public == true
input.action == "read"
}

View File

@@ -1,10 +0,0 @@
package bench
default allow := false
allow if {
input.user.role == "admin"
input.action in ["read", "write", "delete"]
input.resource.classification in ["public", "internal"]
count(input.user.permissions) > 0
}

View File

@@ -1,23 +0,0 @@
package bench
default allow := false
# Time-based access control with complex conditions
business_hours if {
hour := time.clock([time.now_ns(), "America/New_York"])[0]
hour >= 9
hour < 17
}
allow if {
input.user.department in ["engineering", "product"]
input.action == "deploy"
business_hours
count([x | x := input.approvals[_]; x.status == "approved"]) >= 2
}
allow if {
input.user.emergency_access == true
input.action in ["read", "diagnose"]
input.justification != ""
}

View File

@@ -1,560 +0,0 @@
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use regorus::languages::azure_policy::aliases::{denormalizer, normalizer, AliasRegistry};
use regorus::Value;
use serde_json::json;
// ─── Alias catalog (reused across benchmarks) ───────────────────────────────
const ALIASES_JSON: &str = r#"[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
"defaultPath": "properties.securityRules[*].properties.priority",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
"defaultPath": "properties.securityRules[*].properties.direction",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules[*].protocol",
"defaultPath": "properties.defaultSecurityRules[*].properties.protocol",
"paths": []
}
]
}
]
},
{
"namespace": "Microsoft.Storage",
"resourceTypes": [
{
"resourceType": "storageAccounts",
"aliases": [
{
"name": "Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly",
"defaultPath": "properties.supportsHttpsTrafficOnly",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/accessTier",
"defaultPath": "properties.accessTier",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/isHnsEnabled",
"defaultPath": "properties.isHnsEnabled",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/minimumTlsVersion",
"defaultPath": "properties.minimumTlsVersion",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/allowBlobPublicAccess",
"defaultPath": "properties.allowBlobPublicAccess",
"paths": []
},
{
"name": "Microsoft.Storage/storageAccounts/sku.name",
"defaultPath": "sku.name",
"paths": []
}
]
}
]
}
]"#;
fn build_registry() -> AliasRegistry {
let mut reg = AliasRegistry::new();
reg.load_from_json(ALIASES_JSON).unwrap();
reg
}
/// Convert a serde_json::Value to regorus::Value.
fn to_regorus(v: serde_json::Value) -> Value {
Value::from(v)
}
// ─── Input resources ────────────────────────────────────────────────────────
fn simple_storage_resource() -> Value {
to_regorus(json!({
"name": "myStorageAccount",
"type": "Microsoft.Storage/storageAccounts",
"location": "westus2",
"kind": "StorageV2",
"sku": { "name": "Standard_LRS", "tier": "Standard" },
"tags": { "environment": "production", "team": "platform" },
"properties": {
"supportsHttpsTrafficOnly": true,
"accessTier": "Hot",
"isHnsEnabled": false,
"minimumTlsVersion": "TLS1_2",
"allowBlobPublicAccess": false
}
}))
}
fn nsg_resource(rule_count: usize) -> Value {
let rules: Vec<serde_json::Value> = (0..rule_count)
.map(|i| {
json!({
"name": format!("rule-{}", i),
"properties": {
"protocol": "Tcp",
"access": if i % 2 == 0 { "Allow" } else { "Deny" },
"priority": 100 + i,
"direction": "Inbound",
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
"destinationPortRange": format!("{}", 80 + i)
}
})
})
.collect();
to_regorus(json!({
"name": "myNsg",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "eastus",
"properties": {
"securityRules": rules
}
}))
}
// ─── Benchmarks ─────────────────────────────────────────────────────────────
fn bench_normalize_simple(c: &mut Criterion) {
let registry = build_registry();
let resource = simple_storage_resource();
c.bench_function("normalize/simple_storage", |b| {
b.iter(|| normalizer::normalize(black_box(&resource), Some(&registry), None))
});
}
fn bench_normalize_no_aliases(c: &mut Criterion) {
let resource = simple_storage_resource();
c.bench_function("normalize/simple_no_aliases", |b| {
b.iter(|| normalizer::normalize(black_box(&resource), None, None))
});
}
fn bench_normalize_nsg_scaling(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("normalize/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| b.iter(|| normalizer::normalize(black_box(res), Some(&registry), None)),
);
}
group.finish();
}
fn bench_denormalize_simple(c: &mut Criterion) {
let registry = build_registry();
let resource = simple_storage_resource();
let normalized = normalizer::normalize(&resource, Some(&registry), None);
c.bench_function("denormalize/simple_storage", |b| {
b.iter(|| denormalizer::denormalize(black_box(&normalized), Some(&registry), None))
});
}
fn bench_denormalize_nsg_scaling(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("denormalize/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_resource(rule_count);
let normalized = normalizer::normalize(&resource, Some(&registry), None);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&normalized,
|b, norm| b.iter(|| denormalizer::denormalize(black_box(norm), Some(&registry), None)),
);
}
group.finish();
}
fn bench_round_trip(c: &mut Criterion) {
let registry = build_registry();
let resource = nsg_resource(20);
c.bench_function("round_trip/nsg_20_rules", |b| {
b.iter(|| {
let n = normalizer::normalize(black_box(&resource), Some(&registry), None);
denormalizer::denormalize(&n, Some(&registry), None)
})
});
}
fn bench_normalize_and_wrap(c: &mut Criterion) {
let registry = build_registry();
let resource = nsg_resource(20);
let context = to_regorus(json!({"resourceGroup": {"name": "rg1"}}));
let parameters = to_regorus(json!({"env": "prod"}));
c.bench_function("normalize_and_wrap/nsg_20_rules", |b| {
b.iter(|| {
registry.normalize_and_wrap(
black_box(&resource),
None,
Some(context.clone()),
Some(parameters.clone()),
)
})
});
}
fn bench_registry_load(c: &mut Criterion) {
c.bench_function("registry/load_from_json", |b| {
b.iter(|| {
let mut reg = AliasRegistry::new();
reg.load_from_json(black_box(ALIASES_JSON)).unwrap();
reg
})
});
}
// ─── Large-payload benchmarks ───────────────────────────────────────────────
//
// These stress the hot paths identified in the performance analysis:
// - Nested set helpers (alias-heavy catalog with deep properties)
// - Array element remap/cleanup/rewrap (large sub-resource arrays)
// - Scalar denormalization lookups (many aliases × many fields)
/// Build a large alias catalog with `n` scalar aliases for storage accounts.
/// Each alias maps to a nested `properties.section_i.field_j` path, creating
/// deep nested-set workloads.
fn large_alias_catalog(n: usize) -> String {
let mut aliases = Vec::new();
for i in 0..n {
let section = i / 10;
let field = i % 10;
aliases.push(format!(
r#"{{
"name": "Microsoft.Storage/storageAccounts/section{section}Field{field}",
"defaultPath": "properties.section{section}.field{field}",
"paths": []
}}"#,
));
}
format!(
r#"[{{
"namespace": "Microsoft.Storage",
"resourceTypes": [{{
"resourceType": "storageAccounts",
"aliases": [{aliases}]
}}]
}}]"#,
aliases = aliases.join(",")
)
}
/// Build a storage account resource whose `properties` contain nested sections
/// matching the large alias catalog.
fn large_storage_resource(alias_count: usize) -> Value {
let mut sections = serde_json::Map::new();
for i in 0..alias_count {
let section = i / 10;
let field = i % 10;
let section_key = format!("section{section}");
let section_obj = sections
.entry(section_key)
.or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
if let serde_json::Value::Object(m) = section_obj {
m.insert(format!("field{field}"), serde_json::Value::from(i));
}
}
Value::from(json!({
"name": "bigStorage",
"type": "Microsoft.Storage/storageAccounts",
"location": "westus2",
"properties": sections
}))
}
fn bench_normalize_large_catalog(c: &mut Criterion) {
let mut group = c.benchmark_group("normalize/large_catalog");
for alias_count in [50, 200] {
let catalog_json = large_alias_catalog(alias_count);
let mut reg = AliasRegistry::new();
reg.load_from_json(&catalog_json).unwrap();
let resource = large_storage_resource(alias_count);
group.bench_with_input(
BenchmarkId::from_parameter(alias_count),
&(reg, resource),
|b, (reg, res)| b.iter(|| normalizer::normalize(black_box(res), Some(reg), None)),
);
}
group.finish();
}
fn bench_denormalize_large_catalog(c: &mut Criterion) {
let mut group = c.benchmark_group("denormalize/large_catalog");
for alias_count in [50, 200] {
let catalog_json = large_alias_catalog(alias_count);
let mut reg = AliasRegistry::new();
reg.load_from_json(&catalog_json).unwrap();
let resource = large_storage_resource(alias_count);
let normalized = normalizer::normalize(&resource, Some(&reg), None);
group.bench_with_input(
BenchmarkId::from_parameter(alias_count),
&(reg, normalized),
|b, (reg, norm)| b.iter(|| denormalizer::denormalize(black_box(norm), Some(reg), None)),
);
}
group.finish();
}
fn bench_nsg_large_subarrays(c: &mut Criterion) {
let registry = build_registry();
let mut group = c.benchmark_group("round_trip/nsg_sub_resource");
for rule_count in [50, 200, 500] {
let resource = nsg_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
let n = normalizer::normalize(black_box(res), Some(&registry), None);
denormalizer::denormalize(&n, Some(&registry), None)
})
},
);
}
group.finish();
}
// ─── Versioned-path benchmarks ──────────────────────────────────────────────
//
// Exercise the precomputed versioned-path aggregates by building a catalog
// where wildcard (array) aliases have version-specific paths that differ from
// the default, then running normalize/denormalize with an explicit api_version.
/// NSG-like alias catalog where wildcard aliases have versioned paths that
/// differ from the default. This forces the normalize/denormalize path through
/// the versioned aggregate lookup rather than the default-aggregate fast path.
const VERSIONED_ALIASES_JSON: &str = r#"[
{
"namespace": "Microsoft.Network",
"resourceTypes": [
{
"resourceType": "networkSecurityGroups",
"aliases": [
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].protocol",
"defaultPath": "properties.securityRules[*].properties.protocol",
"paths": [
{ "path": "properties.securityRules[*].properties.transportProtocol", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.protocol", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].access",
"defaultPath": "properties.securityRules[*].properties.access",
"paths": [
{ "path": "properties.securityRules[*].properties.accessLevel", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.access", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].priority",
"defaultPath": "properties.securityRules[*].properties.priority",
"paths": [
{ "path": "properties.securityRules[*].properties.rulePriority", "apiVersions": ["2020-01-01"] },
{ "path": "properties.securityRules[*].properties.priority", "apiVersions": ["2022-01-01"] }
]
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].direction",
"defaultPath": "properties.securityRules[*].properties.direction",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].sourceAddressPrefix",
"defaultPath": "properties.securityRules[*].properties.sourceAddressPrefix",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].destinationPortRange",
"defaultPath": "properties.securityRules[*].properties.destinationPortRange",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/securityRules[*].name",
"defaultPath": "properties.securityRules[*].name",
"paths": []
},
{
"name": "Microsoft.Network/networkSecurityGroups/provisioningState",
"defaultPath": "properties.provisioningState",
"paths": [
{ "path": "properties.state", "apiVersions": ["2020-01-01"] },
{ "path": "properties.provisioningState", "apiVersions": ["2022-01-01"] }
]
}
]
}
]
}
]"#;
fn build_versioned_registry() -> AliasRegistry {
let mut reg = AliasRegistry::new();
reg.load_from_json(VERSIONED_ALIASES_JSON).unwrap();
reg
}
/// Build an NSG resource for versioned-path benchmarks.
/// Uses the 2020-01-01 field names (`transportProtocol`, `accessLevel`,
/// `rulePriority`) so that versioned path resolution actually differs from
/// the default.
fn nsg_versioned_resource(rule_count: usize) -> Value {
let rules: Vec<serde_json::Value> = (0..rule_count)
.map(|i| {
json!({
"name": format!("rule-{}", i),
"properties": {
"transportProtocol": "Tcp",
"accessLevel": if i % 2 == 0 { "Allow" } else { "Deny" },
"rulePriority": 100 + i,
"direction": "Inbound",
"sourceAddressPrefix": format!("10.0.{}.0/24", i % 256),
"destinationPortRange": format!("{}", 80 + i)
}
})
})
.collect();
to_regorus(json!({
"name": "myNsg",
"type": "Microsoft.Network/networkSecurityGroups",
"location": "eastus",
"properties": {
"state": "Succeeded",
"securityRules": rules
}
}))
}
fn bench_normalize_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("normalize_versioned/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_versioned_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
normalizer::normalize(black_box(res), Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
fn bench_denormalize_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("denormalize_versioned/nsg_rules");
for rule_count in [5, 20, 100] {
let resource = nsg_versioned_resource(rule_count);
let normalized = normalizer::normalize(&resource, Some(&registry), Some("2020-01-01"));
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&normalized,
|b, norm| {
b.iter(|| {
denormalizer::denormalize(black_box(norm), Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
fn bench_round_trip_versioned(c: &mut Criterion) {
let registry = build_versioned_registry();
let mut group = c.benchmark_group("round_trip_versioned/nsg_rules");
for rule_count in [20, 100] {
let resource = nsg_versioned_resource(rule_count);
group.bench_with_input(
BenchmarkId::from_parameter(rule_count),
&resource,
|b, res| {
b.iter(|| {
let n =
normalizer::normalize(black_box(res), Some(&registry), Some("2020-01-01"));
denormalizer::denormalize(&n, Some(&registry), Some("2020-01-01"))
})
},
);
}
group.finish();
}
criterion_group!(
normalization_benches,
bench_normalize_simple,
bench_normalize_no_aliases,
bench_normalize_nsg_scaling,
bench_denormalize_simple,
bench_denormalize_nsg_scaling,
bench_round_trip,
bench_normalize_and_wrap,
bench_registry_load,
bench_normalize_large_catalog,
bench_denormalize_large_catalog,
bench_nsg_large_subarrays,
bench_normalize_versioned,
bench_denormalize_versioned,
bench_round_trip_versioned,
);
criterion_main!(normalization_benches);

View File

@@ -1,186 +0,0 @@
use std::hint::black_box;
use regorus::{Engine, Value};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde_json::json;
fn engine_with_policy(policy: &str) -> Engine {
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy.to_string())
.unwrap();
engine
}
fn eval_principal(engine: &mut Engine) {
engine.set_input(black_box(json!({"principal": "admin"}).into()));
let result = engine
.eval_rule(black_box("data.bench.allow".to_string()))
.unwrap();
assert_eq!(result, true.into());
}
fn allow_with_simple_equality(c: &mut Criterion) {
c.bench_function("simple equality check with constant", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == "admin"
"#,
);
b.iter(|| eval_principal(&mut engine))
});
c.bench_function("simple equality check with data", |b| {
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal == data.allowed_principal
"#,
);
engine
.add_data(json!({"allowed_principal": "admin"}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
fn allow_with_simple_membership(c: &mut Criterion) {
let generate_principals = |n: usize| {
(0..n)
.map(|i| i.to_string())
.chain(std::iter::once("admin".to_string()))
.collect::<Vec<_>>()
};
let mut group = c.benchmark_group("allow with simple membership");
for size in [32, 64, 128, 512, 1024, 2048].iter() {
group.bench_with_input(BenchmarkId::new("with constant", size), size, |b, &size| {
let principals = generate_principals(size).join("\",\"");
let mut engine = engine_with_policy(&format!(
r#"
package bench
allowed_principals := {{
"{principals}"
}}
allow if input.principal in allowed_principals
"#
));
b.iter(|| eval_principal(&mut engine))
});
group.bench_with_input(BenchmarkId::new("with data", size), size, |b, &size| {
let principals = generate_principals(size);
let mut engine = engine_with_policy(
r#"
package bench
allow if input.principal in data.allowed_principals
"#,
);
engine
.add_data(json!({"allowed_principals": principals}).into())
.unwrap();
b.iter(|| eval_principal(&mut engine))
});
}
group.finish();
}
fn clone(c: &mut Criterion) {
// Use Arc<BtreeMap> as a reference. Clone will only increment
// the reference count.
let mut m = std::collections::BTreeMap::default();
m.insert(1, 2);
let m = std::sync::Arc::new(m);
c.bench_function("clone: Arc<BTreeMap>", |b| {
b.iter(|| {
let _ = m.clone();
})
});
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/framework.rego")
.unwrap();
engine.add_policy_from_file("tests/aci/api.rego").unwrap();
engine
.add_policy_from_file("tests/aci/policy.rego")
.unwrap();
engine
.add_data(Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"))
.expect("failed to add data");
engine.set_input(
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json"),
);
// An engine without preparation will not have processed fields populated.
c.bench_function("clone: engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
// Trigger engine preparation.
let _ = engine.eval_query("data.framework.mount_overlay".to_string(), false);
// Prepared engine will have many more fields populated. But the fields are
// immutable after preparation and will be shared between clones.
c.bench_function("clone: prepared engine with aci policies", |b| {
b.iter(|| {
let _ = engine.clone();
})
});
}
fn aci_policy_eval(c: &mut Criterion) {
let mut group = c.benchmark_group("ACI Policy Eval");
let rules = ["data.policy.mount_overlay", "data.policy.mount_device"];
for rule in rules {
group.bench_with_input(BenchmarkId::new("rule", rule), &rule, |b, rule| {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_policy_from_file("tests/aci/api.rego")
.expect("failed to add api.rego");
engine
.add_policy_from_file("tests/aci/framework.rego")
.expect("failed to add framework.rego");
engine
.add_policy_from_file("tests/aci/policy.rego")
.expect("failed to add policy.rego");
engine
.add_data(
Value::from_json_file("tests/aci/data.json").expect("failed to load data.json"),
)
.expect("failed to add data");
let input =
Value::from_json_file("tests/aci/input.json").expect("failed to load input.json");
engine.set_input(input.clone());
engine.eval_rule(rule.to_string()).unwrap();
b.iter(|| {
engine.eval_rule(rule.to_string()).unwrap();
})
});
}
group.finish();
}
criterion_group!(
benches,
allow_with_simple_equality,
allow_with_simple_membership,
clone,
aci_policy_eval
);
criterion_main!(benches);

View File

@@ -1,680 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Comprehensive RVM benchmarks covering all aspects of the Rego Virtual Machine.
//!
//! # Policy families
//!
//! | Family | Source | Policies | Inputs/policy |
//! |------------|-------------------------------|----------|---------------|
//! | Synthetic | `benches/evaluation/test_data`| 9 | 3 each |
//! | ACI | `tests/aci` | 9 | 1 each |
//!
//! # Benchmark groups
//!
//! | Group | What it measures |
//! |--------------------------|-------------------------------------------------------|
//! | `cold/{case}/{config}` | Cold: new VM + load + data + input + execute |
//! | `hot/{case}/{config}` | Hot: set_input + execute (VM reused across iters) |
//! | `compilation` | Rego CompiledPolicy → RVM Program |
//! | `serialization` | Program binary serialize / deserialize roundtrip |
//! | `startup` | Isolated VM creation & setup overhead |
//! | `stats` | Instruction/literal counts (reported as throughput) |
//! | `end_to_end` | Full roundtrip: compile → serialize → deserialize → eval |
//!
//! # Running subsets
//!
//! ```sh
//! cargo bench --bench rvm_benchmark # everything
//! cargo bench --bench rvm_benchmark -- cold # all cold eval
//! cargo bench --bench rvm_benchmark -- hot # all hot eval
//! cargo bench --bench rvm_benchmark -- regular_with_limits # one config across cases
//! cargo bench --bench rvm_benchmark -- cold/aci/ # all ACI cold benchmarks
//! cargo bench --bench rvm_benchmark -- rbac # one policy family
//! cargo bench --bench rvm_benchmark -- compilation # compilation only
//! cargo bench --bench rvm_benchmark -- serialization # serialization only
//! cargo bench --bench rvm_benchmark -- startup # startup overhead
//! ```
use std::hint::black_box;
use std::num::NonZeroU32;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::program::Program;
use regorus::rvm::vm::{ExecutionMode, RegoVM};
use regorus::utils::limits::ExecutionTimerConfig;
use regorus::{Engine, Rc, Value};
// ---------------------------------------------------------------------------
// Limit constants generous ceilings that still exercise the limit-checking
// hot path (memory_check, execution_timer_tick, instruction-limit compare).
// ---------------------------------------------------------------------------
#[cfg(feature = "allocator-memory-limits")]
const MEMORY_LIMIT_BYTES: u64 = 256 * 1024 * 1024;
const TIME_LIMIT: Duration = Duration::from_secs(30);
const TIMER_CHECK_INTERVAL: NonZeroU32 = NonZeroU32::new(16).unwrap();
const INSTRUCTION_LIMIT: usize = 10_000_000;
#[derive(Clone, Copy)]
struct EvalConfig {
name: &'static str,
mode: ExecutionMode,
limits: bool,
}
const EVAL_CONFIGS: [EvalConfig; 4] = [
EvalConfig {
name: "regular_no_limits",
mode: ExecutionMode::RunToCompletion,
limits: false,
},
EvalConfig {
name: "regular_with_limits",
mode: ExecutionMode::RunToCompletion,
limits: true,
},
EvalConfig {
name: "suspendable_no_limits",
mode: ExecutionMode::Suspendable,
limits: false,
},
EvalConfig {
name: "suspendable_with_limits",
mode: ExecutionMode::Suspendable,
limits: true,
},
];
// ---------------------------------------------------------------------------
// Data types
// ---------------------------------------------------------------------------
/// A compiled benchmark program ready for RVM execution.
struct BenchmarkProgram {
/// Human-readable name (e.g. "rbac_policy" or "aci/create_container").
name: String,
/// Pre-compiled RVM program.
program: Arc<Program>,
/// Compiled policy (kept for compilation benchmarks).
compiled_policy: regorus::CompiledPolicy,
/// Entry-point rule path.
entry_point: String,
/// Data object (Some for policies that require external data like ACI).
data: Option<Value>,
/// Named inputs for this policy.
inputs: Vec<(String, Value)>,
}
// ---------------------------------------------------------------------------
// ACI YAML types
// ---------------------------------------------------------------------------
#[derive(Serialize, Deserialize, Debug)]
struct AciTestCase {
note: String,
data: Value,
input: Value,
modules: Vec<String>,
query: String,
want_result: Value,
}
#[derive(Serialize, Deserialize, Debug)]
struct AciYamlTest {
cases: Vec<AciTestCase>,
}
// ---------------------------------------------------------------------------
// Synthetic policy loading
// ---------------------------------------------------------------------------
/// Policy ↔ input file mapping for synthetic policies.
const SYNTHETIC_POLICIES: &[(&str, &str, &[&str])] = &[
(
"rbac_policy",
"rbac_policy.rego",
&["rbac_input.json", "rbac_input2.json", "rbac_input3.json"],
),
(
"api_access",
"api_access_policy.rego",
&[
"api_access_input.json",
"api_access_input2.json",
"api_access_input3.json",
],
),
(
"data_sensitivity",
"data_sensitivity_policy.rego",
&[
"data_sensitivity_input.json",
"data_sensitivity_input2.json",
"data_sensitivity_input3.json",
],
),
(
"time_based",
"time_based_policy.rego",
&[
"time_based_input.json",
"time_based_input2.json",
"time_based_input3.json",
],
),
(
"data_processing",
"data_processing_policy.rego",
&[
"data_processing_input.json",
"data_processing_input2.json",
"data_processing_input3.json",
],
),
(
"azure_vm",
"azure_vm_policy.rego",
&[
"azure_vm_input.json",
"azure_vm_input2.json",
"azure_vm_input3.json",
],
),
(
"azure_storage",
"azure_storage_policy.rego",
&[
"azure_storage_input.json",
"azure_storage_input2.json",
"azure_storage_input3.json",
],
),
(
"azure_keyvault",
"azure_keyvault_policy.rego",
&[
"azure_keyvault_input.json",
"azure_keyvault_input2.json",
"azure_keyvault_input3.json",
],
),
(
"azure_nsg",
"azure_nsg_policy.rego",
&[
"azure_nsg_input.json",
"azure_nsg_input2.json",
"azure_nsg_input3.json",
],
),
];
/// Compile synthetic Rego policies into RVM programs.
fn compile_synthetic_programs() -> Vec<BenchmarkProgram> {
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
let entry_point = "data.bench.allow";
let entry_point_rc: Rc<str> = entry_point.into();
SYNTHETIC_POLICIES
.iter()
.map(|(name, policy_file, input_files)| {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = std::fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy_content)
.expect("failed to add policy");
let compiled_policy = engine
.compile_with_entrypoint(&entry_point_rc)
.expect("failed to compile policy");
let program = Compiler::compile_from_policy(&compiled_policy, &[entry_point])
.expect("failed to compile to RVM program");
let inputs: Vec<(String, Value)> = input_files
.iter()
.map(|input_file| {
let input_path = base_dir.join("inputs").join(input_file);
let json = std::fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
let value = Value::from_json_str(&json).expect("failed to parse input JSON");
let display = input_file.trim_end_matches(".json").to_string();
(display, value)
})
.collect();
BenchmarkProgram {
name: name.to_string(),
program,
compiled_policy,
entry_point: entry_point.to_string(),
data: None,
inputs,
}
})
.collect()
}
// ---------------------------------------------------------------------------
// ACI policy loading
// ---------------------------------------------------------------------------
/// Load all ACI test cases from YAML files.
fn load_aci_cases(dir: &Path) -> Vec<AciTestCase> {
let mut cases = Vec::new();
for entry in WalkDir::new(dir)
.sort_by_file_name()
.into_iter()
.filter_map(|e| e.ok())
{
let path = entry.path();
if !path.to_string_lossy().ends_with(".yaml") {
continue;
}
let yaml = std::fs::read(path).expect("failed to read yaml");
let yaml = String::from_utf8_lossy(&yaml);
let test: AciYamlTest = serde_yaml::from_str(&yaml).expect("failed to deserialize yaml");
cases.extend(test.cases);
}
cases
}
/// Build an Engine with policies loaded for a given ACI test case.
fn build_aci_engine(dir: &Path, case: &AciTestCase) -> Engine {
let mut engine = Engine::new();
engine.set_rego_v0(true);
engine
.add_data(case.data.clone())
.expect("failed to add data");
engine.set_input(case.input.clone());
for (idx, rego) in case.modules.iter().enumerate() {
if rego.ends_with(".rego") {
engine
.add_policy_from_file(dir.join(rego).to_str().expect("invalid path"))
.expect("failed to add policy");
} else {
engine
.add_policy(format!("rego{idx}.rego"), rego.clone())
.expect("failed to add policy");
}
}
engine
}
/// Compile ACI test cases into RVM programs.
fn compile_aci_programs() -> Vec<BenchmarkProgram> {
let dir = Path::new("tests/aci");
load_aci_cases(dir)
.into_iter()
.map(|case| {
let mut engine = build_aci_engine(dir, &case);
let rule = case.query.replace("=x", "");
let rule_rc: Rc<str> = rule.clone().into();
let compiled_policy = engine
.compile_with_entrypoint(&rule_rc)
.expect("failed to compile");
let program = Compiler::compile_from_policy(&compiled_policy, &[rule.as_str()])
.expect("failed to compile to RVM");
BenchmarkProgram {
name: format!("aci/{}", case.note),
program,
compiled_policy,
entry_point: rule,
data: Some(case.data),
inputs: vec![("input".to_string(), case.input)],
}
})
.collect()
}
// ---------------------------------------------------------------------------
// Compile all policies
// ---------------------------------------------------------------------------
/// Compile all policies (synthetic + ACI) into RVM programs.
fn compile_all_programs() -> Vec<BenchmarkProgram> {
let mut programs = compile_synthetic_programs();
programs.extend(compile_aci_programs());
programs
}
// ---------------------------------------------------------------------------
// Limit helpers
// ---------------------------------------------------------------------------
/// Apply or remove production-style limits based on a boolean flag.
fn configure_limits(vm: &mut RegoVM, limits: bool) {
if limits {
#[cfg(feature = "allocator-memory-limits")]
regorus::set_global_memory_limit(Some(MEMORY_LIMIT_BYTES));
vm.set_execution_timer_config(Some(ExecutionTimerConfig {
limit: TIME_LIMIT,
check_interval: TIMER_CHECK_INTERVAL,
}));
vm.set_max_instructions(INSTRUCTION_LIMIT);
} else {
#[cfg(feature = "allocator-memory-limits")]
regorus::set_global_memory_limit(None);
vm.set_execution_timer_config(None);
vm.set_max_instructions(usize::MAX);
}
}
// ---------------------------------------------------------------------------
// Cold evaluation — new VM per iteration (full setup + execute)
//
// Benchmarks are registered case-first so each workload is shown with all
// config variants adjacent to one another, making per-case comparisons easier.
// ---------------------------------------------------------------------------
fn bench_cold(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("cold");
for bp in &programs {
for (input_name, input_value) in &bp.inputs {
let case_id = if bp.inputs.len() == 1 {
bp.name.clone()
} else {
format!("{}/{}", bp.name, input_name)
};
let program = bp.program.clone();
let data = bp.data.clone();
let input = input_value.clone();
for config in EVAL_CONFIGS {
group.bench_function(BenchmarkId::new(&case_id, config.name), |b| {
b.iter(|| {
let mut vm = RegoVM::new();
vm.set_execution_mode(config.mode);
vm.load_program(black_box(program.clone()));
if let Some(ref d) = data {
vm.set_data(black_box(d.clone())).unwrap();
}
vm.set_input(black_box(input.clone()));
configure_limits(&mut vm, config.limits);
black_box(vm.execute().unwrap())
})
});
}
}
}
group.finish();
}
// ---------------------------------------------------------------------------
// Hot evaluation — VM reused across iterations
//
// The VM is created once with program, data, mode, and limits. Each
// iteration only calls set_input + execute, measuring pure execution
// overhead with minimal setup. A warm-up execution fills the register
// window pool so all iterations benefit from pooled allocations.
// ---------------------------------------------------------------------------
fn bench_hot(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("hot");
for bp in &programs {
let program = bp.program.clone();
let data = bp.data.clone();
let inputs: Vec<Value> = bp.inputs.iter().map(|(_, v)| v.clone()).collect();
let num_inputs = inputs.len();
for config in EVAL_CONFIGS {
group.bench_function(BenchmarkId::new(&bp.name, config.name), |b| {
let mut vm = RegoVM::new();
vm.set_execution_mode(config.mode);
vm.load_program(program.clone());
if let Some(ref d) = data {
vm.set_data(d.clone()).unwrap();
}
configure_limits(&mut vm, config.limits);
// Warm up: fill register window pools, caches, etc.
vm.set_input(inputs[0].clone());
vm.execute().expect("warm-up failed");
let mut i = 0usize;
b.iter(|| {
let input = &inputs[i % num_inputs];
vm.set_input(black_box(input.clone()));
black_box(vm.execute().unwrap());
i += 1;
})
});
}
}
group.finish();
}
// ---------------------------------------------------------------------------
// Compilation — Rego CompiledPolicy → RVM Program
// ---------------------------------------------------------------------------
fn bench_compilation(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("compilation");
for bp in &programs {
let entry_point: &str = &bp.entry_point;
group.bench_with_input(
BenchmarkId::new("rego_to_rvm", &bp.name),
&bp.compiled_policy,
|b, compiled_policy| {
b.iter(|| {
Compiler::compile_from_policy(
black_box(compiled_policy),
black_box(&[entry_point]),
)
.unwrap();
})
},
);
}
group.finish();
}
// ---------------------------------------------------------------------------
// Serialization — binary serialize / deserialize roundtrip
// ---------------------------------------------------------------------------
fn bench_serialization(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("serialization");
for bp in &programs {
let program = &bp.program;
let serialized = program
.serialize_binary()
.expect("failed to serialize program");
let byte_len = serialized.len() as u64;
group.throughput(Throughput::Bytes(byte_len));
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
b.iter(|| black_box(program.serialize_binary().unwrap()))
});
group.throughput(Throughput::Bytes(byte_len));
group.bench_function(BenchmarkId::new("deserialize", &bp.name), |b| {
b.iter(|| black_box(Program::deserialize_binary(black_box(&serialized)).unwrap()))
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Startup — isolated VM creation & setup overhead
// ---------------------------------------------------------------------------
fn bench_startup(c: &mut Criterion) {
let programs = compile_all_programs();
let mut group = c.benchmark_group("startup");
// Use the first program as representative for startup overhead.
let bp = &programs[0];
let program = bp.program.clone();
let input = bp.inputs[0].1.clone();
// Bare VM creation
group.bench_function("new", |b| b.iter(|| black_box(RegoVM::new())));
// load_program (Arc clone + internal setup)
group.bench_function("load_program", |b| {
b.iter(|| {
let mut vm = RegoVM::new();
vm.load_program(black_box(program.clone()));
black_box(&vm);
})
});
// set_input
group.bench_function("set_input", |b| {
let mut vm = RegoVM::new();
vm.load_program(program.clone());
b.iter(|| {
vm.set_input(black_box(input.clone()));
})
});
group.finish();
}
// ---------------------------------------------------------------------------
// Stats — instruction / literal counts (reported as throughput)
// ---------------------------------------------------------------------------
fn bench_stats(c: &mut Criterion) {
let programs = compile_all_programs();
eprintln!();
eprintln!(
"{:<30} {:>8} {:>8} {:>8} {:>10}",
"program", "instrs", "lits", "entries", "bytes"
);
eprintln!("{}", "-".repeat(70));
let mut group = c.benchmark_group("stats");
for bp in &programs {
let serialized = bp.program.serialize_binary().expect("serialize failed");
let byte_len = serialized.len();
let instr_count = bp.program.instructions.len();
let lit_count = bp.program.literals.len();
let entry_count = bp.program.entry_points.len();
eprintln!(
"{:<30} {:>8} {:>8} {:>8} {:>10}",
bp.name, instr_count, lit_count, entry_count, byte_len,
);
group.throughput(Throughput::Elements(instr_count as u64));
group.bench_function(BenchmarkId::new("serialize", &bp.name), |b| {
b.iter(|| black_box(bp.program.serialize_binary().unwrap()))
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// End-to-end roundtrip (compile + serialize + deserialize + eval)
//
// Only runs for synthetic policies where we have direct access to rego
// source files. ACI policies are loaded from YAML with module references
// which makes the setup pipeline different.
// ---------------------------------------------------------------------------
fn bench_end_to_end(c: &mut Criterion) {
let base_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("benches")
.join("evaluation")
.join("test_data");
let entry_point = "data.bench.allow";
let entry_point_rc: Rc<str> = entry_point.into();
let mut group = c.benchmark_group("end_to_end");
for &(name, policy_file, input_files) in SYNTHETIC_POLICIES {
let policy_path = base_dir.join("policies").join(policy_file);
let policy_content = std::fs::read_to_string(&policy_path)
.unwrap_or_else(|e| panic!("Failed to read {policy_path:?}: {e}"));
// Use just the first input for end-to-end
let input_path = base_dir.join("inputs").join(input_files[0]);
let input_json = std::fs::read_to_string(&input_path)
.unwrap_or_else(|e| panic!("Failed to read {input_path:?}: {e}"));
group.bench_function(BenchmarkId::new("roundtrip", name), |b| {
b.iter(|| {
// 1. Engine + parse
let mut engine = Engine::new();
engine
.add_policy("policy.rego".to_string(), policy_content.clone())
.unwrap();
// 2. Compile to CompiledPolicy
let compiled_policy = engine.compile_with_entrypoint(&entry_point_rc).unwrap();
// 3. Compile to RVM Program
let program =
Compiler::compile_from_policy(&compiled_policy, &[entry_point]).unwrap();
// 4. Serialize
let bytes = program.serialize_binary().unwrap();
// 5. Deserialize
let deserialized = Program::deserialize_binary(&bytes).unwrap();
let program = match deserialized {
regorus::rvm::program::DeserializationResult::Complete(p) => Arc::new(p),
regorus::rvm::program::DeserializationResult::Partial(p) => {
Arc::new(Program::compile_from_partial(p).unwrap())
}
};
// 6. Execute
let mut vm = RegoVM::new();
vm.load_program(program);
let input = Value::from_json_str(&input_json).unwrap();
vm.set_input(input);
black_box(vm.execute().unwrap());
})
});
}
group.finish();
}
// ---------------------------------------------------------------------------
// Criterion groups — organised for selective runs
// ---------------------------------------------------------------------------
criterion_group!(cold_benches, bench_cold);
criterion_group!(hot_benches, bench_hot);
criterion_group!(
misc_benches,
bench_compilation,
bench_serialization,
bench_startup,
bench_stats,
bench_end_to_end,
);
criterion_main!(cold_benches, hot_benches, misc_benches);

View File

@@ -1,886 +0,0 @@
use criterion::{criterion_group, criterion_main, Criterion};
use regorus::Value;
use regorus::{Schema, SchemaValidator};
use serde_json::json;
// Observed: validate_string - 3.19 ns/iter
fn bench_string_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"minLength": 3,
"maxLength": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("hello");
c.bench_function("validate_string", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_number - 146.5 ns/iter
fn bench_number_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "number",
"minimum": 0.0,
"maximum": 100.0
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(42.5);
c.bench_function("validate_number", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array - 95.0 ns/iter
fn bench_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "integer" },
"minItems": 2,
"maxItems": 5
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!([1, 2, 3]));
c.bench_function("validate_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_object - 126.9 ns/iter
fn bench_object_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name", "age"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({"name": "Alice", "age": 30}));
c.bench_function("validate_object", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_complex_nested - 710.5 ns/iter
fn bench_complex_nested_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"id": { "type": "string" },
"profile": {
"type": "object",
"properties": {
"email": { "type": "string" },
"roles": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["email", "roles"]
}
},
"required": ["id", "profile"]
},
"active": { "type": "boolean" }
},
"required": ["user", "active"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"id": "u123",
"profile": {
"email": "alice@example.com",
"roles": ["admin", "user"]
}
},
"active": true
}));
c.bench_function("validate_complex_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_string_pattern - 29.99 µs/iter
fn bench_string_pattern_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "string",
"pattern": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("user@example.com");
c.bench_function("validate_string_pattern", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_enum - 7.26 ns/iter
fn bench_enum_validation(c: &mut Criterion) {
let schema_json = json!({
"enum": ["pending", "approved", "rejected", "cancelled"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from("approved");
c.bench_function("validate_enum", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_boolean - 3.22 ns/iter
fn bench_boolean_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "boolean"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(true);
c.bench_function("validate_boolean", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_null - 3.22 ns/iter
fn bench_null_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "null"
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::Null;
c.bench_function("validate_null", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_large_array - 17.30 µs/iter
fn bench_large_array_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "number" },
"minItems": 50,
"maxItems": 200
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let large_array: Vec<_> = (0..100).map(|i| json!(i as f64)).collect();
let value = Value::from(json!(large_array));
c.bench_function("validate_large_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_deeply_nested - 468.2 ns/iter
fn bench_deeply_nested_object(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"level1": {
"type": "object",
"properties": {
"level2": {
"type": "object",
"properties": {
"level3": {
"type": "object",
"properties": {
"level4": {
"type": "object",
"properties": {
"level5": {
"type": "string"
}
},
"required": ["level5"]
}
},
"required": ["level4"]
}
},
"required": ["level3"]
}
},
"required": ["level2"]
}
},
"required": ["level1"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"level1": {
"level2": {
"level3": {
"level4": {
"level5": "deep value"
}
}
}
}
}));
c.bench_function("validate_deeply_nested", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_mixed_type_array - 1.36 µs/iter
fn bench_mixed_type_array(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "type": "number" },
{ "type": "boolean" }
]
}
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["hello", 42, true, "world", 99.5, false]));
c.bench_function("validate_mixed_type_array", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_additional_properties - 366.4 ns/iter
fn bench_additional_properties(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"additionalProperties": { "type": "string" },
"required": ["name"]
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"name": "Alice",
"age": 30,
"city": "New York",
"country": "USA",
"occupation": "Engineer"
}));
c.bench_function("validate_additional_properties", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_array_constraints - 146.2 ns/iter
fn bench_array_constraints(c: &mut Criterion) {
let schema_json = json!({
"type": "array",
"items": { "type": "string" },
"minItems": 2,
"maxItems": 10
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!(["apple", "banana", "cherry", "date", "elderberry"]));
c.bench_function("validate_array_constraints", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_multi_level - 915.8 ns/iter
fn bench_multi_level_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"user": {
"type": "object",
"properties": {
"profile": {
"type": "object",
"properties": {
"settings": {
"type": "object",
"properties": {
"theme": {
"enum": ["light", "dark", "auto"]
},
"notifications": {
"type": "boolean"
}
},
"required": ["theme"],
"additionalProperties": { "type": "string" }
}
},
"required": ["settings"],
"additionalProperties": { "type": "any" }
}
},
"required": ["profile"],
"additionalProperties": { "type": "any" }
}
},
"required": ["user"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"user": {
"profile": {
"settings": {
"theme": "dark",
"notifications": true,
"language": "en"
},
"avatar": "default.png"
},
"lastLogin": "2024-01-01"
},
"metadata": "extra info"
}));
c.bench_function("validate_multi_level", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Resource Validation Benchmarks
// Observed: validate_azure_vm_resource - 34.74 µs/iter
fn bench_azure_vm_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Compute/virtualMachines"
},
"apiVersion": {
"enum": ["2021-03-01", "2021-07-01", "2022-03-01"]
},
"name": {
"type": "string",
"pattern": "^[a-zA-Z0-9-._]{1,64}$"
},
"location": {
"type": "string",
"description": "Azure region where the VM will be deployed"
},
"properties": {
"type": "object",
"properties": {
"hardwareProfile": {
"type": "object",
"properties": {
"vmSize": {
"enum": ["Standard_B1s", "Standard_B2s", "Standard_D2s_v3", "Standard_D4s_v3"]
}
},
"required": ["vmSize"]
},
"osProfile": {
"type": "object",
"properties": {
"computerName": {
"type": "string"
},
"adminUsername": {
"type": "string"
}
},
"required": ["computerName", "adminUsername"]
}
},
"required": ["hardwareProfile", "osProfile"]
}
},
"required": ["type", "apiVersion", "name", "location", "properties"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "my-vm-01",
"location": "eastus",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B2s"
},
"osProfile": {
"computerName": "my-computer",
"adminUsername": "azureuser"
}
}
}));
c.bench_function("validate_azure_vm_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_storage_resource - 22.12 µs/iter
fn bench_azure_storage_resource_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"type": {
"const": "Microsoft.Storage/storageAccounts"
},
"apiVersion": {
"enum": ["2021-04-01", "2021-06-01", "2022-05-01"]
},
"name": {
"type": "string",
"pattern": "^[a-z0-9]{3,24}$"
},
"location": {
"type": "string"
},
"sku": {
"type": "object",
"properties": {
"name": {
"enum": ["Standard_LRS", "Standard_GRS", "Standard_RAGRS", "Premium_LRS"]
}
},
"required": ["name"]
},
"kind": {
"enum": ["Storage", "StorageV2", "BlobStorage", "FileStorage", "BlockBlobStorage"]
},
"properties": {
"type": "object",
"properties": {
"accessTier": {
"enum": ["Hot", "Cool", "Archive"]
},
"encryption": {
"type": "object",
"properties": {
"services": {
"type": "object"
}
}
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["type", "apiVersion", "name", "location", "sku", "kind"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "mystorageaccount001",
"location": "westus2",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"encryption": {
"services": {}
}
}
}));
c.bench_function("validate_azure_storage_resource", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_arm_template - 1.99 µs/iter
fn bench_azure_arm_template_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"$schema": {
"type": "string"
},
"contentVersion": {
"type": "string"
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"variables": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"resources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"apiVersion": {
"type": "string"
},
"name": {
"type": "string"
},
"location": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"tags": {
"type": "object",
"additionalProperties": { "type": "string" }
}
},
"required": ["type", "apiVersion", "name"],
"additionalProperties": { "type": "any" }
}
},
"outputs": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["resources"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"vmName": {
"type": "string",
"defaultValue": "myVM"
}
},
"variables": {
"storageAccountName": "[concat('storage', uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Compute/virtualMachines",
"apiVersion": "2021-03-01",
"name": "[parameters('vmName')]",
"location": "[resourceGroup().location]",
"properties": {
"hardwareProfile": {
"vmSize": "Standard_B1s"
}
},
"tags": {
"environment": "dev",
"project": "test"
}
}
],
"outputs": {
"vmId": {
"type": "string",
"value": "[resourceId('Microsoft.Compute/virtualMachines', parameters('vmName'))]"
}
}
}));
c.bench_function("validate_azure_arm_template", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Azure Policy Effect Validation Benchmarks
// Observed: validate_azure_policy_deny_effect - 188.6 ns/iter
fn bench_azure_policy_deny_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "deny"
},
"description": {
"type": "string"
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deny",
"description": "Deny resources that don't meet security requirements"
}));
c.bench_function("validate_azure_policy_deny_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_audit_effect - 516.7 ns/iter
fn bench_azure_policy_audit_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "audit"
},
"description": {
"type": "string"
},
"auditDetails": {
"type": "object",
"properties": {
"category": {
"enum": ["security", "compliance", "cost", "operational"]
},
"severity": {
"enum": ["low", "medium", "high", "critical"]
}
},
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "audit",
"description": "Audit non-compliant resources",
"auditDetails": {
"category": "security",
"severity": "high"
}
}));
c.bench_function("validate_azure_policy_audit_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_modify_effect - 1.17 µs/iter
fn bench_azure_policy_modify_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"const": "modify"
},
"description": {
"type": "string"
},
"modifyDetails": {
"type": "object",
"properties": {
"roleDefinitionIds": {
"type": "array",
"items": { "type": "string" }
},
"operations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"operation": {
"enum": ["add", "replace", "remove"]
},
"field": {
"type": "string"
},
"value": {
"type": "any"
}
},
"required": ["operation", "field"],
"additionalProperties": { "type": "any" }
}
}
},
"required": ["roleDefinitionIds", "operations"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect", "modifyDetails"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "modify",
"description": "Modify resources to ensure compliance",
"modifyDetails": {
"roleDefinitionIds": [
"/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c"
],
"operations": [
{
"operation": "add",
"field": "tags.environment",
"value": "production"
}
]
}
}));
c.bench_function("validate_azure_policy_modify_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
// Observed: validate_azure_policy_complex_effect - 1.40 µs/iter
fn bench_azure_policy_complex_effect_validation(c: &mut Criterion) {
let schema_json = json!({
"type": "object",
"properties": {
"effect": {
"enum": ["auditIfNotExists", "deployIfNotExists"]
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"existenceCondition": {
"type": "object",
"properties": {
"field": { "type": "string" },
"equals": { "type": "string" }
},
"required": ["field"],
"additionalProperties": { "type": "any" }
},
"deployment": {
"type": "object",
"properties": {
"properties": {
"type": "object",
"properties": {
"mode": {
"enum": ["incremental", "complete"]
},
"template": {
"type": "object",
"additionalProperties": { "type": "any" }
},
"parameters": {
"type": "object",
"additionalProperties": { "type": "any" }
}
},
"required": ["mode", "template"],
"additionalProperties": { "type": "any" }
}
},
"required": ["properties"],
"additionalProperties": { "type": "any" }
}
},
"required": ["effect"],
"additionalProperties": { "type": "any" }
});
let schema = Schema::from_serde_json_value(schema_json).unwrap();
let value = Value::from(json!({
"effect": "deployIfNotExists",
"parameters": {},
"existenceCondition": {
"field": "Microsoft.Security/complianceResults/resourceStatus",
"equals": "OffByPolicy"
},
"deployment": {
"properties": {
"mode": "incremental",
"template": {
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"resources": []
},
"parameters": {}
}
}
}));
c.bench_function("validate_azure_policy_complex_effect", |b| {
b.iter(|| {
SchemaValidator::validate(&value, &schema).unwrap();
})
});
}
criterion_group!(
schema_validation_benches,
bench_string_validation,
bench_number_validation,
bench_array_validation,
bench_object_validation,
bench_complex_nested_validation,
bench_string_pattern_validation,
bench_enum_validation,
bench_boolean_validation,
bench_null_validation,
bench_large_array_validation,
bench_deeply_nested_object,
bench_mixed_type_array,
bench_additional_properties,
bench_array_constraints,
bench_multi_level_validation,
bench_azure_vm_resource_validation,
bench_azure_storage_resource_validation,
bench_azure_arm_template_validation,
bench_azure_policy_deny_effect_validation,
bench_azure_policy_audit_effect_validation,
bench_azure_policy_modify_effect_validation,
bench_azure_policy_complex_effect_validation
);
criterion_main!(schema_validation_benches);

View File

@@ -1,126 +1,97 @@
#include <stdio.h>
#include <stdlib.h>
#if defined(_WIN32)
#include <malloc.h>
#endif
#include "regorus.h"
// Regorus has been built for no_std and cannot access files.
char *file_to_string(const char *file)
{
char *buffer = 0;
char* file_to_string(const char* file) {
char * buffer = 0;
long length;
FILE *f = fopen(file, "rb");
FILE * f = fopen (file, "rb");
if (f)
{
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
buffer = malloc(length + 1);
buffer[length] = '\0';
if (buffer)
{
fread(buffer, 1, length, f);
}
fclose(f);
fseek (f, 0, SEEK_END);
length = ftell (f);
fseek (f, 0, SEEK_SET);
buffer = malloc (length + 1);
buffer[length] = '\0';
if (buffer)
{
fread (buffer, 1, length, f);
}
fclose (f);
}
return buffer;
}
// If regorus is built with custom-allocator, then provide implementation.
uint8_t *regorus_aligned_alloc(size_t alignment, size_t size)
{
// Aligned allocations must respect platform quirks: Windows offers
// _aligned_malloc/_aligned_free, while macOS/Linux reject aligned_alloc
// calls when size is not a multiple of alignment, so we rely on
// posix_memalign for the no_std build.
#if defined(_WIN32)
return (uint8_t *)_aligned_malloc(size, alignment);
#else
void *ptr = NULL;
// posix_memalign requires alignment to be at least sizeof(void*)
// and a power of two; normalize here so small requests succeed.
if (alignment < sizeof(void *))
{
alignment = sizeof(void *);
}
if (posix_memalign(&ptr, alignment, size) != 0)
{
return NULL;
}
return (uint8_t *)ptr;
#endif
uint8_t* regorus_aligned_alloc(size_t alignment, size_t size) {
return (uint8_t*) aligned_alloc(alignment, size);
}
void regorus_free(uint8_t *ptr)
{
#if defined(_WIN32)
_aligned_free(ptr);
#else
void regorus_free(uint8_t* ptr) {
free(ptr);
#endif
}
int main()
{
int main() {
// Create engine.
RegorusEngine *engine = regorus_engine_new();
RegorusEngine* engine = regorus_engine_new();
RegorusResult r;
char *buffer = NULL;
char* buffer = NULL;
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
// Load policies.
r = regorus_engine_add_policy(engine, "framework.rego", (buffer = file_to_string("../../../tests/aci/framework.rego")));
free(buffer);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "api.rego", (buffer = file_to_string("../../../tests/aci/api.rego")));
free(buffer);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy(engine, "policy.rego", (buffer = file_to_string("../../../tests/aci/policy.rego")));
free(buffer);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_json(engine, (buffer = file_to_string("../../../tests/aci/data.json")));
free(buffer);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_json(engine, (buffer = file_to_string("../../../tests/aci/input.json")));
free(buffer);
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_rule(engine, "data.framework.mount_overlay");
if (r.status != Ok)
goto error;
if (r.status != RegorusStatusOk)
goto error;
// Print output
printf("%s", r.output);
regorus_result_drop(r);
// Free the engine.
regorus_engine_drop(engine);

View File

@@ -13,7 +13,6 @@ FetchContent_Declare(
FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
enable_testing()
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
@@ -36,10 +35,3 @@ add_executable(regorus_test main.c)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)
add_executable(regorus_rvm_test rvm_tests.c)
target_include_directories(regorus_rvm_test PRIVATE "../ffi")
target_link_libraries(regorus_rvm_test regorus_ffi)
add_test(NAME regorus_c_engine COMMAND regorus_test)
add_test(NAME regorus_c_rvm COMMAND regorus_rvm_test)

View File

@@ -8,57 +8,43 @@ int main() {
// Turn on rego v0 since policy uses v0.
r = regorus_engine_set_rego_v0(engine, true);
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
// Configure the global pattern caches.
RegorusCacheConfig cache_config = { .regex = 256, .glob = 128 };
r = regorus_set_cache_config(cache_config);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Raise the default col limit to 2000
RegorusPolicyLengthConfig len_config = { .max_col = 2000, .max_file_bytes = 1048576, .max_lines = 20000 };
r = regorus_engine_set_policy_length_config(engine, len_config);
if (r.status != Ok)
goto error;
regorus_result_drop(r);
// Load policies.
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/framework.rego");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/api.rego");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
r = regorus_engine_add_policy_from_file(engine, "../../../tests/aci/policy.rego");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
printf("Loaded package %s\n", r.output);
regorus_result_drop(r);
// Add data
r = regorus_engine_add_data_from_json_file(engine, "../../../tests/aci/data.json");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Set input
r = regorus_engine_set_input_from_json_file(engine, "../../../tests/aci/input.json");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
regorus_result_drop(r);
// Eval rule.
r = regorus_engine_eval_query(engine, "data.framework.mount_overlay");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
// Print output
@@ -80,14 +66,14 @@ int main() {
);
// Evaluate rule.
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
r = regorus_engine_set_enable_coverage(engine, true);
regorus_result_drop(r);
r = regorus_engine_eval_query(engine, "data.test.message");
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
// Print output
@@ -96,7 +82,7 @@ int main() {
// Print pretty coverage report.
r = regorus_engine_get_coverage_report_pretty(engine);
if (r.status != Ok)
if (r.status != RegorusStatusOk)
goto error;
printf("%s\n", r.output);

View File

@@ -1,289 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include <stdio.h>
#include <string.h>
#include "regorus.h"
static int assert_ok(RegorusResult r, const char* message) {
if (r.status != Ok) {
fprintf(stderr, "%s: %s\n", message, r.error_message ? r.error_message : "(no error)");
return 0;
}
return 1;
}
int main() {
RegorusResult result = {0};
bool result_valid = false;
RegorusProgram* program = NULL;
RegorusBuffer* buffer = NULL;
RegorusProgram* program2 = NULL;
RegorusRvm* vm = NULL;
RegorusProgram* host_program = NULL;
RegorusRvm* host_vm = NULL;
bool is_partial = false;
int exit_code = 1;
const char* data_json =
"{"
" \"roles\": {"
" \"alice\": [\"admin\", \"reader\"]"
" }"
"}";
const char* input_json =
"{"
" \"user\": \"alice\","
" \"actions\": [\"read\"]"
"}";
const char* module_text =
"package demo\n"
"default allow = false\n"
"allow if {\n"
" input.user == \"alice\"\n"
" some role in data.roles[input.user]\n"
" role == \"admin\"\n"
" count(input.actions) > 0\n"
"}\n";
const char* host_data_json = "{}";
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
const char* host_module_text =
"package demo\n"
"import rego.v1\n"
"default allow := false\n"
"allow if {\n"
" input.account.active == true\n"
" details := __builtin_host_await(input.account.id, \"account\")\n"
" details.tier == \"gold\"\n"
"}\n";
RegorusPolicyModule module;
module.id = "demo.rego";
module.content = module_text;
const char* entry_points[] = {"data.demo.allow"};
printf("Rego policy:\n%s\n", module_text);
printf("Compiling program from modules...\n");
result = regorus_program_compile_from_modules(
data_json,
&module,
1,
entry_points,
1
);
result_valid = true;
if (!assert_ok(result, "compile program")) {
goto Cleanup;
}
program = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Generating assembly listing...\n");
result = regorus_program_generate_listing(program);
result_valid = true;
if (!assert_ok(result, "generate listing")) {
goto Cleanup;
}
printf("Assembly listing:\n%s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
printf("Serializing program...\n");
result = regorus_program_serialize_binary(program);
result_valid = true;
if (!assert_ok(result, "serialize program")) {
goto Cleanup;
}
buffer = (RegorusBuffer*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Deserializing program (%zu bytes)...\n", buffer->len);
result = regorus_program_deserialize_binary(
buffer->data,
buffer->len,
&is_partial
);
result_valid = true;
if (!assert_ok(result, "deserialize program")) {
goto Cleanup;
}
if (is_partial) {
fprintf(stderr, "deserialized program marked partial\n");
goto Cleanup;
}
program2 = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
printf("Creating VM...\n");
vm = regorus_rvm_new();
if (!vm) {
fprintf(stderr, "failed to allocate VM\n");
goto Cleanup;
}
printf("Loading program into VM...\n");
result = regorus_rvm_load_program(vm, program2);
result_valid = true;
if (!assert_ok(result, "load program")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Setting data...\n");
result = regorus_rvm_set_data(vm, data_json);
result_valid = true;
if (!assert_ok(result, "set data")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Setting input...\n");
result = regorus_rvm_set_input(vm, input_json);
result_valid = true;
if (!assert_ok(result, "set input")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
printf("Executing entry point...\n");
result = regorus_rvm_execute(vm);
result_valid = true;
if (!assert_ok(result, "execute")) {
goto Cleanup;
}
printf("Execution result (data.demo.allow): %s\n",
result.output ? result.output : "(null)");
printf("Decision: user=alice action=read -> allow=%s\n",
result.output ? result.output : "(null)");
if (!result.output || strcmp(result.output, "true") != 0) {
fprintf(stderr, "unexpected result: %s\n", result.output);
goto Cleanup;
}
printf("\n--- HostAwait example (suspendable execution) ---\n");
RegorusPolicyModule host_module;
host_module.id = "host_await.rego";
host_module.content = host_module_text;
const char* host_entry_points[] = {"data.demo.allow"};
result = regorus_program_compile_from_modules(
host_data_json,
&host_module,
1,
host_entry_points,
1
);
result_valid = true;
if (!assert_ok(result, "compile host await program")) {
goto Cleanup;
}
host_program = (RegorusProgram*)result.pointer_value;
regorus_result_drop(result);
result_valid = false;
host_vm = regorus_rvm_new();
if (!host_vm) {
fprintf(stderr, "failed to allocate host await VM\n");
goto Cleanup;
}
result = regorus_rvm_set_execution_mode(host_vm, 1);
result_valid = true;
if (!assert_ok(result, "set execution mode")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_load_program(host_vm, host_program);
result_valid = true;
if (!assert_ok(result, "load host await program")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_set_data(host_vm, host_data_json);
result_valid = true;
if (!assert_ok(result, "set host data")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_set_input(host_vm, host_input_json);
result_valid = true;
if (!assert_ok(result, "set host input")) {
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_execute(host_vm);
result_valid = true;
if (!assert_ok(result, "execute host await")) {
goto Cleanup;
}
printf("HostAwait initial result: %s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_get_execution_state(host_vm);
result_valid = true;
if (!assert_ok(result, "get execution state")) {
goto Cleanup;
}
printf("Execution state: %s\n", result.output ? result.output : "(null)");
regorus_result_drop(result);
result_valid = false;
result = regorus_rvm_resume(host_vm, "{\"tier\":\"gold\"}", true);
result_valid = true;
if (!assert_ok(result, "resume host await")) {
goto Cleanup;
}
printf("HostAwait resumed result: %s\n", result.output ? result.output : "(null)");
if (!result.output || strcmp(result.output, "true") != 0) {
fprintf(stderr, "unexpected host await result\n");
goto Cleanup;
}
regorus_result_drop(result);
result_valid = false;
exit_code = 0;
Cleanup:
if (result_valid) {
regorus_result_drop(result);
}
if (host_vm) {
regorus_rvm_drop(host_vm);
}
if (host_program) {
regorus_program_drop(host_program);
}
if (vm) {
regorus_rvm_drop(vm);
}
if (program2) {
regorus_program_drop(program2);
}
if (buffer) {
regorus_buffer_drop(buffer);
}
if (program) {
regorus_program_drop(program);
}
return exit_code;
}

View File

@@ -14,9 +14,6 @@ FetchContent_MakeAvailable(Corrosion)
project("regorus-test")
set(CMAKE_CXX_STANDARD 17)
enable_testing()
# installable ffi target
corrosion_import_crate(
# Path to <regorus-source-folder>/bindings/ffi/Cargo.toml
@@ -34,59 +31,7 @@ corrosion_import_crate(
# Link statically
CRATE_TYPES "cdylib")
include(GNUInstallDirs)
set(regorus_ffi_INCLUDEDIR ${CMAKE_INSTALL_INCLUDEDIR}/regorus_ffi)
set(regorus_ffi_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/regorus_ffi)
set(regorus_ffi_LIBDIR ${CMAKE_INSTALL_LIBDIR})
set(regorus_ffi_BINDIR ${CMAKE_INSTALL_BINDIR})
add_library(regorus_ffi::regorus_ffi ALIAS regorus_ffi)
corrosion_install(TARGETS regorus_ffi EXPORT regorus_ffi_targets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
target_include_directories(regorus_ffi
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../ffi>
$<INSTALL_INTERFACE:${regorus_ffi_INCLUDEDIR}>
)
set(regorus_ffi_HEADER_FILES
regorus.hpp
../ffi/regorus.ffi.hpp
)
install(FILES ${regorus_ffi_HEADER_FILES}
DESTINATION ${regorus_ffi_INCLUDEDIR}
COMPONENT Devel
)
install(EXPORT regorus_ffi_targets
FILE regorus_ffi_targets.cmake
NAMESPACE regorus_ffi::
DESTINATION ${regorus_ffi_CONFIGDIR}
)
include(CMakePackageConfigHelpers)
configure_package_config_file(${CMAKE_CURRENT_LIST_DIR}/regorus_ffiConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
INSTALL_DESTINATION ${regorus_ffi_CONFIGDIR}
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/regorus_ffiConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/corrosion/regorus_ffi_targetsCorrosion.cmake
DESTINATION ${regorus_ffi_CONFIGDIR}
)
# test binary
add_executable(regorus_test main.cpp)
target_link_libraries(regorus_test regorus_ffi::regorus_ffi)
add_executable(regorus_rvm_test rvm_tests.cpp)
target_link_libraries(regorus_rvm_test regorus_ffi::regorus_ffi)
add_test(NAME regorus_cpp_engine COMMAND regorus_test)
add_test(NAME regorus_cpp_rvm COMMAND regorus_rvm_test)
# Add path to <regorus-source-folder>/bindings/ffi
target_include_directories(regorus_test PRIVATE "../ffi")
target_link_libraries(regorus_test regorus_ffi)

View File

@@ -6,19 +6,8 @@ void example()
// Create engine
regorus::Engine engine;
// Configure the global pattern caches.
RegorusCacheConfig cache_config = { 256, 128 };
regorus::set_cache_config(cache_config);
engine.set_rego_v0(true);
engine.set_enable_coverage(true);
RegorusPolicyLengthConfig len_config;
// Raise the default col limit to 2000
len_config.max_col = 2000;
len_config.max_file_bytes = 1048576;
len_config.max_lines = 20000;
engine.set_policy_length_config(len_config);
// Add policies.
engine.add_policy("objects.rego",R"(package objects

View File

@@ -1,8 +1,6 @@
#ifndef REGORUS_WRAPPER_HPP
#define REGORUS_WRAPPER_HPP
#include <cstddef>
#include <cstdint>
#include <memory>
#include <variant>
@@ -10,14 +8,11 @@
namespace regorus {
class Buffer;
class Program;
class Result {
public:
class Result {
public:
operator bool() const { return result.status == RegorusStatus::Ok; }
bool operator !() const { return result.status != RegorusStatus::Ok; }
operator bool() const { return result.status == RegorusStatus::RegorusStatusOk; }
bool operator !() const { return result.status != RegorusStatus::RegorusStatusOk; }
const char* output() const {
if (*this && result.output) {
@@ -35,39 +30,18 @@ namespace regorus {
}
}
void* pointer() const {
return result.pointer_value;
}
Program program() const;
Buffer buffer() const;
Result(RegorusResult r) : result(r) {}
Result(Result&& other) noexcept : result(other.result) {
other.result.output = nullptr;
other.result.error_message = nullptr;
other.result.pointer_value = nullptr;
}
Result& operator=(Result&& other) noexcept {
if (this != &other) {
regorus_result_drop(result);
result = other.result;
other.result.output = nullptr;
other.result.error_message = nullptr;
other.result.pointer_value = nullptr;
}
return *this;
}
~Result() {
regorus_result_drop(result);
}
private:
friend class Engine;
RegorusResult result;
Result(RegorusResult r) : result(r) {}
private:
Result(const Result&) = delete;
Result(Result&&) = delete;
Result& operator=(const Result&) = delete;
};
@@ -131,22 +105,10 @@ namespace regorus {
Result get_coverage_report_pretty() {
return Result(regorus_engine_get_coverage_report_pretty(engine));
}
Result set_policy_length_config(RegorusPolicyLengthConfig config) {
return Result(regorus_engine_set_policy_length_config(engine, config));
}
Result clear_policy_length_config() {
return Result(regorus_engine_clear_policy_length_config(engine));
}
~Engine() {
regorus_engine_drop(engine);
}
RegorusEngine* raw() const {
return engine;
}
private:
@@ -157,255 +119,6 @@ namespace regorus {
Engine(Engine&&) = delete;
Engine& operator=(const Engine&) = delete;
};
inline Result set_cache_config(RegorusCacheConfig config) {
return Result(regorus_set_cache_config(config));
}
inline Result clear_cache() {
return Result(regorus_clear_cache());
}
class CompiledPolicy {
public:
explicit CompiledPolicy(RegorusCompiledPolicy* p) : policy(p) {}
Result eval_with_input(const char* input_json) {
return Result(regorus_compiled_policy_eval_with_input(policy, input_json));
}
Result get_policy_info() {
return Result(regorus_compiled_policy_get_policy_info(policy));
}
RegorusCompiledPolicy* raw() const {
return policy;
}
~CompiledPolicy() {
if (policy) {
regorus_compiled_policy_drop(policy);
}
}
private:
RegorusCompiledPolicy* policy;
CompiledPolicy(const CompiledPolicy&) = delete;
CompiledPolicy(CompiledPolicy&&) = delete;
CompiledPolicy& operator=(const CompiledPolicy&) = delete;
};
class Buffer {
public:
Buffer() : buffer(nullptr) {}
explicit Buffer(RegorusBuffer* b) : buffer(b) {}
const std::uint8_t* data() const {
return buffer ? buffer->data : nullptr;
}
size_t size() const {
return buffer ? buffer->len : 0;
}
RegorusBuffer* raw() const {
return buffer;
}
~Buffer() {
if (buffer) {
regorus_buffer_drop(buffer);
}
}
private:
RegorusBuffer* buffer;
Buffer(const Buffer&) = delete;
Buffer(Buffer&&) = delete;
Buffer& operator=(const Buffer&) = delete;
};
class Program {
public:
Program() : program(regorus_program_new()) {}
explicit Program(RegorusProgram* p) : program(p) {}
static Result compile_from_policy(
RegorusCompiledPolicy* compiled_policy,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_program_compile_from_policy(
compiled_policy,
entry_points,
entry_points_len
));
}
static Result compile_from_modules(
const char* data_json,
const RegorusPolicyModule* modules,
size_t modules_len,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_program_compile_from_modules(
data_json,
modules,
modules_len,
entry_points,
entry_points_len
));
}
static Result compile_from_engine(
RegorusEngine* engine,
const char* const* entry_points,
size_t entry_points_len
) {
return Result(regorus_engine_compile_program_with_entrypoints(
engine,
entry_points,
entry_points_len
));
}
Result serialize_binary() const {
return Result(regorus_program_serialize_binary(program));
}
static Result deserialize_binary(
const std::uint8_t* data,
size_t len,
bool* is_partial
) {
return Result(regorus_program_deserialize_binary(data, len, is_partial));
}
Result generate_listing() const {
return Result(regorus_program_generate_listing(program));
}
Result generate_tabular_listing() const {
return Result(regorus_program_generate_tabular_listing(program));
}
RegorusProgram* raw() const {
return program;
}
~Program() {
if (program) {
regorus_program_drop(program);
}
}
private:
RegorusProgram* program;
Program(const Program&) = delete;
Program(Program&&) = delete;
Program& operator=(const Program&) = delete;
};
inline Program Result::program() const {
return Program(reinterpret_cast<RegorusProgram*>(result.pointer_value));
}
inline Buffer Result::buffer() const {
return Buffer(reinterpret_cast<RegorusBuffer*>(result.pointer_value));
}
class Rvm {
public:
Rvm() : vm(regorus_rvm_new()) {}
explicit Rvm(RegorusRvm* v) : vm(v) {}
static Result create_with_policy(RegorusCompiledPolicy* compiled_policy) {
return Result(regorus_rvm_new_with_policy(compiled_policy));
}
Result load_program(const Program& program) {
return Result(regorus_rvm_load_program(vm, program.raw()));
}
Result set_data(const char* data_json) {
return Result(regorus_rvm_set_data(vm, data_json));
}
Result set_input(const char* input_json) {
return Result(regorus_rvm_set_input(vm, input_json));
}
Result set_max_instructions(size_t max_instructions) {
return Result(regorus_rvm_set_max_instructions(vm, max_instructions));
}
Result set_strict_builtin_errors(bool strict) {
return Result(regorus_rvm_set_strict_builtin_errors(vm, strict));
}
Result set_execution_mode(std::uint8_t mode) {
return Result(regorus_rvm_set_execution_mode(vm, mode));
}
Result set_step_mode(bool enabled) {
return Result(regorus_rvm_set_step_mode(vm, enabled));
}
Result set_execution_timer_config(bool has_config, RegorusExecutionTimerConfig config) {
return Result(regorus_rvm_set_execution_timer_config(vm, has_config, config));
}
Result execute() {
return Result(regorus_rvm_execute(vm));
}
Result execute_entry_point_by_name(const char* entry_point) {
return Result(regorus_rvm_execute_entry_point_by_name(vm, entry_point));
}
Result execute_entry_point_by_index(size_t index) {
return Result(regorus_rvm_execute_entry_point_by_index(vm, index));
}
Result resume(const char* resume_value_json, bool has_value) {
return Result(regorus_rvm_resume(vm, resume_value_json, has_value));
}
Result get_execution_state() {
return Result(regorus_rvm_get_execution_state(vm));
}
RegorusRvm* raw() const {
return vm;
}
~Rvm() {
if (vm) {
regorus_rvm_drop(vm);
}
}
private:
RegorusRvm* vm;
Rvm(const Rvm&) = delete;
Rvm(Rvm&&) = delete;
Rvm& operator=(const Rvm&) = delete;
};
inline Result compile_policy_with_entrypoint(
const char* data_json,
const RegorusPolicyModule* modules,
size_t modules_len,
const char* entry_point
) {
return Result(regorus_compile_policy_with_entrypoint(
data_json,
modules,
modules_len,
entry_point
));
}
}
#endif // REGORUS_WRAPPER_HPP

View File

@@ -1,3 +0,0 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/regorus_ffi_targetsCorrosion.cmake")

View File

@@ -1,261 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include <iostream>
#include <string>
#include "regorus.hpp"
int main() {
const char* data_json =
"{"
" \"roles\": {"
" \"alice\": [\"admin\", \"reader\"]"
" }"
"}";
const char* input_json =
"{"
" \"user\": \"alice\","
" \"actions\": [\"read\"]"
"}";
const char* module_text =
"package demo\n"
"default allow = false\n"
"allow if {\n"
" input.user == \"alice\"\n"
" some role in data.roles[input.user]\n"
" role == \"admin\"\n"
" count(input.actions) > 0\n"
"}\n";
const char* host_data_json = "{}";
const char* host_input_json = "{\"account\":{\"id\":\"acct-1\",\"active\":true}}";
const char* host_module_text =
"package demo\n"
"import rego.v1\n"
"default allow := false\n"
"allow if {\n"
" input.account.active == true\n"
" details := __builtin_host_await(input.account.id, \"account\")\n"
" details.tier == \"gold\"\n"
"}\n";
RegorusPolicyModule module;
module.id = "demo.rego";
module.content = module_text;
const char* entry_points[] = {"data.demo.allow"};
std::cout << "Rego policy:\n" << module_text << std::endl;
std::cout << "Compiling program from modules..." << std::endl;
auto program_result = regorus::Program::compile_from_modules(
data_json,
&module,
1,
entry_points,
1
);
if (!program_result) {
std::cerr << "compile program (modules): " << program_result.error() << std::endl;
return 1;
}
regorus::Program program = program_result.program();
std::cout << "Generating assembly listing..." << std::endl;
auto listing_result = program.generate_listing();
if (!listing_result) {
std::cerr << "generate listing: " << listing_result.error() << std::endl;
return 1;
}
std::cout << "Assembly listing:\n" << listing_result.output() << std::endl;
std::cout << "Serializing program..." << std::endl;
auto serialize_result = program.serialize_binary();
if (!serialize_result) {
std::cerr << "serialize program: " << serialize_result.error() << std::endl;
return 1;
}
regorus::Buffer buffer(reinterpret_cast<RegorusBuffer*>(serialize_result.pointer()));
bool is_partial = false;
std::cout << "Deserializing program (" << buffer.size() << " bytes)..." << std::endl;
auto deserialize_result = regorus::Program::deserialize_binary(
buffer.data(),
buffer.size(),
&is_partial
);
if (!deserialize_result) {
std::cerr << "deserialize program: " << deserialize_result.error() << std::endl;
return 1;
}
if (is_partial) {
std::cerr << "deserialized program marked partial" << std::endl;
return 1;
}
regorus::Program program2 = deserialize_result.program();
{
std::cout << "Creating VM..." << std::endl;
regorus::Rvm vm;
auto load_result = vm.load_program(program2);
if (!load_result) {
std::cerr << "load program: " << load_result.error() << std::endl;
return 1;
}
std::cout << "Setting data..." << std::endl;
auto data_result = vm.set_data(data_json);
if (!data_result) {
std::cerr << "set data: " << data_result.error() << std::endl;
return 1;
}
std::cout << "Setting input..." << std::endl;
auto input_result = vm.set_input(input_json);
if (!input_result) {
std::cerr << "set input: " << input_result.error() << std::endl;
return 1;
}
std::cout << "Executing entry point..." << std::endl;
auto exec_result = vm.execute();
if (!exec_result) {
std::cerr << "execute: " << exec_result.error() << std::endl;
return 1;
}
std::cout << "Execution result (data.demo.allow): " << exec_result.output() << std::endl;
std::cout << "Decision: user=alice action=read -> allow=" << exec_result.output() << std::endl;
if (std::string(exec_result.output()) != "true") {
std::cerr << "unexpected result: " << exec_result.output() << std::endl;
return 1;
}
}
regorus::Engine engine;
std::cout << "Compiling program from engine..." << std::endl;
auto add_policy_result = engine.add_policy("demo.rego", module_text);
if (!add_policy_result) {
std::cerr << "engine add policy: " << add_policy_result.error() << std::endl;
return 1;
}
auto engine_program_result = regorus::Program::compile_from_engine(
engine.raw(),
entry_points,
1
);
if (!engine_program_result) {
std::cerr << "compile program (engine): " << engine_program_result.error() << std::endl;
return 1;
}
regorus::Program engine_program = engine_program_result.program();
regorus::Rvm engine_vm;
auto engine_load_result = engine_vm.load_program(engine_program);
if (!engine_load_result) {
std::cerr << "engine load program: " << engine_load_result.error() << std::endl;
return 1;
}
std::cout << "Setting engine data..." << std::endl;
auto engine_data_result = engine_vm.set_data(data_json);
if (!engine_data_result) {
std::cerr << "engine set data: " << engine_data_result.error() << std::endl;
return 1;
}
std::cout << "Setting engine input..." << std::endl;
auto engine_input_result = engine_vm.set_input(input_json);
if (!engine_input_result) {
std::cerr << "engine set input: " << engine_input_result.error() << std::endl;
return 1;
}
std::cout << "Executing engine entry point..." << std::endl;
auto engine_exec_result = engine_vm.execute();
if (!engine_exec_result) {
std::cerr << "engine execute: " << engine_exec_result.error() << std::endl;
return 1;
}
std::cout << "Engine execution result (data.demo.allow): " << engine_exec_result.output() << std::endl;
std::cout << "Decision: user=alice action=read -> allow=" << engine_exec_result.output() << std::endl;
if (std::string(engine_exec_result.output()) != "true") {
std::cerr << "unexpected engine result: " << engine_exec_result.output() << std::endl;
return 1;
}
std::cout << "\n--- HostAwait example (suspendable execution) ---" << std::endl;
RegorusPolicyModule host_module;
host_module.id = "host_await.rego";
host_module.content = host_module_text;
const char* host_entry_points[] = {"data.demo.allow"};
auto host_program_result = regorus::Program::compile_from_modules(
host_data_json,
&host_module,
1,
host_entry_points,
1
);
if (!host_program_result) {
std::cerr << "compile host await program: " << host_program_result.error() << std::endl;
return 1;
}
regorus::Program host_program = host_program_result.program();
regorus::Rvm host_vm;
auto host_mode_result = host_vm.set_execution_mode(1);
if (!host_mode_result) {
std::cerr << "set execution mode: " << host_mode_result.error() << std::endl;
return 1;
}
auto host_load_result = host_vm.load_program(host_program);
if (!host_load_result) {
std::cerr << "load host await program: " << host_load_result.error() << std::endl;
return 1;
}
auto host_data_result = host_vm.set_data(host_data_json);
if (!host_data_result) {
std::cerr << "set host data: " << host_data_result.error() << std::endl;
return 1;
}
auto host_input_result = host_vm.set_input(host_input_json);
if (!host_input_result) {
std::cerr << "set host input: " << host_input_result.error() << std::endl;
return 1;
}
auto host_exec_result = host_vm.execute();
if (!host_exec_result) {
std::cerr << "execute host await: " << host_exec_result.error() << std::endl;
return 1;
}
std::cout << "HostAwait initial result: " << host_exec_result.output() << std::endl;
auto host_state_result = host_vm.get_execution_state();
if (!host_state_result) {
std::cerr << "get execution state: " << host_state_result.error() << std::endl;
return 1;
}
std::cout << "Execution state: " << host_state_result.output() << std::endl;
auto host_resume_result = host_vm.resume("{\"tier\":\"gold\"}", true);
if (!host_resume_result) {
std::cerr << "resume host await: " << host_resume_result.error() << std::endl;
return 1;
}
std::cout << "HostAwait resumed result: " << host_resume_result.output() << std::endl;
if (std::string(host_resume_result.output()) != "true") {
std::cerr << "unexpected host await result: " << host_resume_result.output() << std::endl;
return 1;
}
return 0;
}

View File

@@ -1,421 +0,0 @@
# Regorus C# API Documentation
This document describes the C# API for Regorus, focusing on the compiled policy approach for high-performance policy evaluation.
## Overview
The Regorus C# bindings provide a modern, thread-safe API for compiling and evaluating Open Policy Agent (OPA) Rego policies. The API is designed around pre-compiled policies that can be evaluated efficiently multiple times with different inputs.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ CompiledPolicy Workflow │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Policy Modules │ │ Target/Schema │ │ Static Data │
│ (.rego files) │ │ Registries │ │ (JSON) │
└─────────┬───────┘ └────────┬─────────┘ └─────────┬───────┘
│ │ │
└─────────────────────┼────────────────────────┘
┌─────────────────────────┐
│ Compile │
│ ┌─────────────────────┐│
│ │ Parse & Analyze ││
│ │ Infer Resource Types││
│ │ Build AST & Rules ││
│ │ Target Integration ││
│ └─────────────────────┘│
└─────────────┬───────────┘
┌─────────────────────────┐
│ CompiledPolicy │
│ ┌─────────────────────┐ │
│ │ AST & Rules │ │
│ │ Target Info │ │
│ │ Resource Types │ │
│ │ Function Table │ │
│ │ Compiled Modules │ │
│ └─────────────────────┘ │
└─────────────┬───────────┘
┌─────────────────────┐
│ Service Cache │
│ (Policy Framework, │
│ MS Graph, etc.) │
│ ┌─────────────────┐ │
│ │ CompiledPolicy │ │ ◄─── Same LOCK-FREE policy
│ │ (cached) │ │ instance shared across
│ └─────────────────┘ │ all threads
└─────────┬───────────┘
┌───────┼───────┬───────┐
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Thread 1 │ │ Thread 2 │ │ Thread N │
│ │ │ │ │ │
│ input1 ────▶│ │ input2 ────▶│ │ inputN ────▶│
│ ◄─── result │ │ ◄─── result │ │ ◄─── result │
└─────────────┘ └─────────────┘ └─────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Key Benefits │
├─────────────────────────────────────────────────────────────────┤
│ ✓ Compile Once, Evaluate Many ✓ Lock-Free Concurrent Eval │
│ ✓ No Re-parsing Overhead ✓ Reference Counting Safety │
│ ✓ Reduced GC Pressure ✓ Proper Resource Management │
│ ✓ Cache-Friendly Design ✓ Target System Integration │
└─────────────────────────────────────────────────────────────────┘
```
## Key Features
- **Pre-compiled Policies**: Compile once, evaluate many times for optimal performance
- **Target System Support**: Built-in support for Azure Policy targets with resource type inference
- **Thread Safety**: All operations are thread-safe without external synchronization
- **Registry Management**: Centralized management of targets and schemas
- **Policy Introspection**: Rich metadata about compiled policies
## Core Classes
### CompiledPolicy
The `CompiledPolicy` class represents a pre-compiled Rego policy that can be evaluated efficiently.
```csharp
public sealed class CompiledPolicy : IDisposable
{
// Evaluate the policy with input data
public string? EvalWithInput(string inputJson);
// Get comprehensive policy metadata
public PolicyInfo GetPolicyInfo();
// Dispose of unmanaged resources
public void Dispose();
}
```
**Thread Safety**: All methods are thread-safe. Multiple threads can call `EvalWithInput()` concurrently, and `Dispose()` will safely wait for active evaluations to complete.
### Compiler
The `Compiler` class provides static methods for compiling policies.
```csharp
public static class Compiler
{
// Compile a policy with a specific entrypoint rule
public static CompiledPolicy CompilePolicyWithEntrypoint(
string dataJson,
IEnumerable<PolicyModule> modules,
string entryPointRule);
// Compile a target-aware policy (requires azure_policy feature)
public static CompiledPolicy CompilePolicyForTarget(
string dataJson,
IEnumerable<PolicyModule> modules);
}
```
### PolicyModule
Represents a single policy module to be compiled. Each PolicyModule corresponds to a Rego file (.rego), and each Rego file defines a Rego package using the `package` declaration at the top of the file.
```csharp
public struct PolicyModule
{
public string Id { get; set; }
public string Content { get; set; }
public PolicyModule(string id, string content);
}
```
**Properties:**
- `Id`: A unique identifier for the module, typically the filename (e.g., "policy.rego", "rules/storage.rego")
- `Content`: The complete Rego policy content, including the `package` declaration and all rules
**Example:**
```csharp
var module = new PolicyModule("storage-policy.rego", @"
package azure.storage
import rego.v1
default allow := false
allow if input.type == ""Microsoft.Storage/storageAccounts""
");
```
### PolicyInfo
Provides comprehensive metadata about a compiled policy.
```csharp
public class PolicyInfo
{
// List of module identifiers
public List<string> ModuleIds { get; set; }
// Target name (for target-aware policies)
public string? TargetName { get; set; }
// Resource types this policy can evaluate
public List<string> ApplicableResourceTypes { get; set; }
// Primary rule/entrypoint
public string EntrypointRule { get; set; }
// Effect rule (for target-aware policies)
public string? EffectRule { get; set; }
// Policy parameters
public List<PolicyParameters> Parameters { get; set; }
}
```
## Registry Classes
### TargetRegistry
Manages target definitions for Azure Policy-style evaluations.
```csharp
public static class TargetRegistry
{
// Register a target from JSON
public static void RegisterFromJson(string targetJson);
// Check if a target exists
public static bool Contains(string name);
// List all registered targets
public static string ListNames();
// Remove a target
public static bool Remove(string name);
// Clear all targets
public static void Clear();
// Get count of registered targets
public static int Count { get; }
// Check if registry is empty
public static bool IsEmpty { get; }
}
```
### SchemaRegistry
Manages schema definitions for validation.
```csharp
public static class SchemaRegistry
{
// Register resource schemas
public static void RegisterResourceSchema(string name, string schemaJson);
public static bool ContainsResourceSchema(string name);
public static string ListResourceSchemas();
// Register effect schemas
public static void RegisterEffectSchema(string name, string schemaJson);
public static bool ContainsEffectSchema(string name);
public static string ListEffectSchemas();
// Clear methods
public static void ClearResourceSchemas();
public static void ClearEffectSchemas();
}
```
## Usage Examples
### Basic Policy Compilation and Evaluation
```csharp
// Define policy modules
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package example
import rego.v1
default allow := false
allow if input.user == ""admin""
")
};
// Compile the policy
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.example.allow");
// Evaluate with different inputs
var result1 = policy.EvalWithInput(@"{""user"": ""admin""}"); // true
var result2 = policy.EvalWithInput(@"{""user"": ""guest""}"); // false
```
### Target-Aware Policy (Azure Policy Style)
```csharp
// Register target definition
TargetRegistry.RegisterFromJson(@"{
""name"": ""azure.storage"",
""resource_schema_selector"": ""type"",
""resource_types"": {
""Microsoft.Storage/storageAccounts"": {
""schema"": { /* JSON Schema */ }
}
}
}");
// Define policy with target
var modules = new List<PolicyModule>
{
new PolicyModule("policy.rego", @"
package policy
import rego.v1
__target__ := ""azure.storage""
default effect := ""deny""
effect := ""allow"" if {
input.type == ""Microsoft.Storage/storageAccounts""
input.properties.supportsHttpsTrafficOnly == true
}
")
};
// Compile for target
using var policy = Compiler.CompilePolicyForTarget("{}", modules);
// Evaluate Azure resource
var resource = @"{
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": {
""supportsHttpsTrafficOnly"": true
}
}";
var result = policy.EvalWithInput(resource); // "allow"
```
### Policy Introspection
```csharp
// Get policy metadata
var info = policy.GetPolicyInfo();
Console.WriteLine($"Target: {info.TargetName}");
Console.WriteLine($"Effect Rule: {info.EffectRule}");
Console.WriteLine($"Modules: {string.Join(", ", info.ModuleIds)}");
Console.WriteLine($"Resource Types: {string.Join(", ", info.ApplicableResourceTypes)}");
// Access parameters
if (info.Parameters != null && info.Parameters.Count > 0)
{
foreach (var parameterSet in info.Parameters)
{
Console.WriteLine($"Module: {parameterSet.SourceFile}");
foreach (var param in parameterSet.Parameters)
{
Console.WriteLine($"Parameter: {param.Name} ({param.Type})");
if (param.Default != null)
Console.WriteLine($" Default: {param.Default}");
}
}
}
```
### Concurrent Evaluation
```csharp
// CompiledPolicy is thread-safe
var tasks = Enumerable.Range(0, 100).Select(i =>
Task.Run(() => policy.EvalWithInput($@"{{""id"": {i}}}"))
).ToArray();
var results = await Task.WhenAll(tasks);
```
## Performance Considerations
### Compilation Overhead
- Policy compilation has significant overhead due to parsing and analysis
- **Best Practice**: Compile once, reuse many times
- Consider caching compiled policies for repeated use
### Memory Management
- `CompiledPolicy` manages unmanaged resources
- **Always** dispose of compiled policies using `using` statements or explicit `Dispose()`
- Disposal is thread-safe and waits for active evaluations
### Thread Safety
- All classes are thread-safe for concurrent reads/evaluations
- Registry modifications should be done during initialization
- No external synchronization required
## Error Handling
All methods throw `Exception` on errors with descriptive messages:
```csharp
try
{
var policy = Compiler.CompilePolicyWithEntrypoint(data, modules, rule);
var result = policy.EvalWithInput(input);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
```
## Feature Flags
Some functionality requires specific Rust feature flags:
- **azure_policy**: Required for target-aware compilation and policy parameters
- Without this feature, target-related methods will not be available
## Version Compatibility
- Requires .NET Standard 2.0 or later
- Compatible with .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+
- Uses System.Text.Json for JSON serialization (added as dependency)
## Best Practices
1. **Compile Once, Evaluate Many**: Pre-compile policies for repeated evaluation
2. **Use Disposable Pattern**: Always dispose of CompiledPolicy instances
3. **Thread-Safe Design**: Take advantage of built-in thread safety
4. **Registry Setup**: Configure targets and schemas during application startup
5. **Error Handling**: Wrap operations in try-catch blocks for robust error handling
6. **Performance Monitoring**: Monitor evaluation times for performance optimization
## Migration from Engine-Based API
If migrating from an engine-based approach:
```csharp
// Old approach (if it existed)
// var engine = new Engine();
// engine.AddPolicy("policy.rego", policyContent);
// engine.SetInputJson(inputJson);
// var result = engine.EvalRule("data.policy.allow");
// New compiled approach
var modules = new[] { new PolicyModule("policy.rego", policyContent) };
using var policy = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.policy.allow");
var result = policy.EvalWithInput(inputJson);
```
The compiled approach provides better performance for repeated evaluations and clearer resource management.

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>Enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<PropertyGroup>
<!-- If the environment variable is set (such as in a Github Action run), append the suffix to the version number -->
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Regorus" />
</ItemGroup>
<ItemGroup>
<None Include="../../ffi/target/release/libregorus_ffi.dylib" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -1,312 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class CompiledPolicyEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
private static List<CompiledPolicy> PrepareSharedCompiledPolicies()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var compiledPolicies = new List<CompiledPolicy>();
foreach (var (policy, _) in policiesWithInputs)
{
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
compiledPolicies.Add(compiled);
}
return compiledPolicies;
}
public static void RunCompiledPolicyEvaluationBenchmark()
{
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running compiled policy benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
// Benchmark both shared policies and per-iteration compilation
var configurations = new[]
{
(true, "compiled_shared_policies"),
(false, "compiled_per_iteration")
};
foreach (var (useSharedPolicies, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunCompiledPolicyBenchmark(threads, useSharedPolicies, groupName);
}
Console.WriteLine();
}
}
public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicies, string groupName)
{
const int warmupSeconds = 3;
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
List<CompiledPolicy>? compiledPolicies = null;
if (useSharedPolicies)
{
compiledPolicies = PrepareSharedCompiledPolicies();
}
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
if (totalEvaluations > 0)
{
var bytesPerEval = allocatedBytes / (double)totalEvaluations;
Console.WriteLine($" alloc: [{bytesPerEval:F2} B/op] (total {allocatedBytes} B)");
}
// Clean up compiled policies if we created them
if (compiledPolicies != null)
{
foreach (var policy in compiledPolicies)
{
DisposeCompiledPolicy(policy);
}
}
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
List<CompiledPolicy>? compiledPolicies,
bool useSharedPolicies,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
long allocatedBytes = 0;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
long allocationStart = 0;
if (!isWarmup)
{
allocationStart = GC.GetAllocatedBytesForCurrentThread();
}
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// Measure only the evaluation call
var evalStopwatch = Stopwatch.StartNew();
if (useSharedPolicies)
{
var result = compiledPolicies![policyIdx].EvalWithInput(input);
}
else
{
// Compile policy in each iteration.
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
try
{
var result = compiled.EvalWithInput(input);
}
finally
{
DisposeCompiledPolicy(compiled);
}
}
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
var allocationEnd = GC.GetAllocatedBytesForCurrentThread();
System.Threading.Interlocked.Add(ref allocatedBytes, allocationEnd - allocationStart);
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
}
private static void DisposeCompiledPolicy(CompiledPolicy policy)
{
try
{
policy.Dispose();
}
catch (TimeoutException ex)
{
Console.WriteLine($"Warning: {ex.Message}");
}
}
}
}

View File

@@ -1,293 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Regorus;
namespace Benchmarks
{
public class EngineEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFiles = new[]
{
("rbac_policy.rego", new[] { "rbac_input.json", "rbac_input2.json", "rbac_input3.json" }),
("api_access_policy.rego", new[] { "api_access_input.json", "api_access_input2.json", "api_access_input3.json" }),
("data_sensitivity_policy.rego", new[] { "data_sensitivity_input.json", "data_sensitivity_input2.json", "data_sensitivity_input3.json" }),
("time_based_policy.rego", new[] { "time_based_input.json", "time_based_input2.json", "time_based_input3.json" }),
("data_processing_policy.rego", new[] { "data_processing_input.json", "data_processing_input2.json", "data_processing_input3.json" }),
("azure_vm_policy.rego", new[] { "azure_vm_input.json", "azure_vm_input2.json", "azure_vm_input3.json" }),
("azure_storage_policy.rego", new[] { "azure_storage_input.json", "azure_storage_input2.json", "azure_storage_input3.json" }),
("azure_keyvault_policy.rego", new[] { "azure_keyvault_input.json", "azure_keyvault_input2.json", "azure_keyvault_input3.json" }),
("azure_nsg_policy.rego", new[] { "azure_nsg_input.json", "azure_nsg_input2.json", "azure_nsg_input3.json" })
};
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
"azure_vm_policy",
"azure_storage_policy",
"azure_keyvault_policy",
"azure_nsg_policy"
};
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();
foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);
var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();
result.Add((policy, inputs));
}
return result;
}
private static List<Engine> PrepareClonedEngines()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var engines = new List<Engine>();
foreach (var (policy, _) in policiesWithInputs)
{
var engine = new Engine();
engine.AddPolicy("policy.rego", policy);
// Warm up the engine to ensure it's fully prepared for evaluation
// This prevents each cloned engine from repeating preparation work
engine.SetInputJson("{}");
try
{
engine.EvalRule("data.bench.allow");
}
catch
{
// Ignore warmup errors
}
engines.Add(engine);
}
return engines;
}
public static void RunEngineEvaluationBenchmark()
{
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };
// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}
Console.WriteLine($"Running engine benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
// Benchmark both cloned engines and fresh engines
var configurations = new[]
{
(true, "cloned_engines"),
(false, "fresh_engines")
};
foreach (var (useClonedEngines, groupName) in configurations)
{
Console.WriteLine($"=== {groupName} ===");
foreach (var threads in threadCounts)
{
RunEngineEvaluationBenchmark(threads, useClonedEngines, groupName);
}
Console.WriteLine();
}
}
public static void RunEngineEvaluationBenchmark(int threads, bool useClonedEngines, string groupName)
{
const int warmupSeconds = 3;
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");
// Warmup phase
var (_, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, useClonedEngines, isWarmup: true);
Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");
// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, useClonedEngines, isWarmup: false);
// Calculate throughput based on pure evaluation time (consistent with Rust benchmark)
var evalsPerSecond = totalEvaluations / evaluationTime.TotalSeconds;
var kelemsPerSecond = evalsPerSecond / 1000.0;
Console.WriteLine($"{groupName}/eval/{threads} threads");
Console.WriteLine($" time: [{evaluationTime.TotalMilliseconds:F2} ms]");
Console.WriteLine($" thrpt: [{kelemsPerSecond:F2} Kelem/s]");
// Verify that all policies were evaluated
var allEvaluated = policyCounters.Values.All(count => count > 0);
if (allEvaluated)
{
Console.WriteLine("✓ All policies were evaluated successfully");
}
else
{
Console.WriteLine("ERROR: Some policies were never evaluated successfully!");
}
}
private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters) RunBenchmarkPhase(
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
bool useClonedEngines,
bool isWarmup)
{
var barrier = new Barrier(threads);
var tasks = new Task[threads];
var policyCounters = new Dictionary<string, int>();
var evaluationTimes = new Dictionary<int, TimeSpan>();
var lockObject = new object();
var stopExecution = false;
// Initialize counters
foreach (var policyName in PolicyNames)
{
policyCounters[policyName] = 0;
}
// Pre-create engines if using cloned engines
List<Engine>? clonedEngines = null;
if (useClonedEngines)
{
clonedEngines = PrepareClonedEngines();
}
var stopwatch = Stopwatch.StartNew();
for (int threadId = 0; threadId < threads; threadId++)
{
int tid = threadId;
tasks[threadId] = Task.Run(() =>
{
barrier.SignalAndWait();
int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;
while (!stopExecution)
{
// Use different policy for each iteration
int policyIdx = (tid + evaluationCount) % policiesWithInputs.Count;
var (policy, inputs) = policiesWithInputs[policyIdx];
// Use different input for the same policy based on iteration
int inputIdx = evaluationCount % inputs.Length;
var input = inputs[inputIdx];
try
{
// Measure only the engine operations
var evalStopwatch = Stopwatch.StartNew();
Engine engine;
if (useClonedEngines)
{
engine = clonedEngines![policyIdx].Clone();
}
else
{
engine = new Engine();
engine.AddPolicy("policy.rego", policy);
}
engine.SetInputJson(input);
var result = engine.EvalRule("data.bench.allow");
engine.Dispose();
evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;
// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
lock (lockObject)
{
policyCounters[PolicyNames[policyIdx]]++;
}
}
}
catch (Exception)
{
// Ignore evaluation errors for benchmarking purposes
}
evaluationCount++;
}
// Store the actual evaluation time for this thread
if (!isWarmup)
{
lock (lockObject)
{
if (!evaluationTimes.ContainsKey(tid))
evaluationTimes[tid] = TimeSpan.Zero;
evaluationTimes[tid] = localEvaluationTime;
}
}
});
}
// Stop execution after the specified duration
Task.Delay(TimeSpan.FromSeconds(durationSeconds)).ContinueWith(_ => stopExecution = true);
Task.WaitAll(tasks);
stopwatch.Stop();
// Clean up cloned engines if we created them
if (clonedEngines != null)
{
foreach (var engine in clonedEngines)
{
engine.Dispose();
}
}
var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);
// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;
return (totalEvaluations, evaluationTime, policyCounters);
}
}
}

View File

@@ -1,36 +0,0 @@
using System;
namespace Benchmarks
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("=== Regorus C# Benchmarks ===\n");
try
{
Console.WriteLine("Running Engine Evaluation Benchmark...");
EngineEvaluationBenchmark.RunEngineEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Engine benchmark failed: {ex.Message}");
}
Console.WriteLine("\n" + new string('=', 80) + "\n");
try
{
Console.WriteLine("Running Compiled Policy Evaluation Benchmark...");
CompiledPolicyEvaluationBenchmark.RunCompiledPolicyEvaluationBenchmark();
}
catch (Exception ex)
{
Console.WriteLine($"Compiled policy benchmark failed: {ex.Message}");
}
Console.WriteLine("\n=== Benchmarks Complete ===");
}
}
}

View File

@@ -1,140 +0,0 @@
# Compiled Policy Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# compiled policy evaluation benchmark tests Regorus compiled policy performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of compiled policy compilation strategies.
## Configuration Combinations
1. **Compiled Shared Policies**: All threads share pre-compiled policy instances - optimal for performance
2. **Compiled Per Iteration**: Each thread compiles the policy for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on compilation strategies with consistent input handling.*
## Performance Results
### Compiled Shared Policies (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2905.41 | 273 |
| 2 | 5808.07 | 240 |
| 4 | 11631.23 | 227 |
| 6 | 17431.95 | 216 |
| 8 | 23183.42 | 126 |
| 10 | 28886.11 | 118 |
| 12 | 34659.87 | 108 |
| 14 | 40564.07 | 84 |
| 16 | 46446.38 | 72 |
| 18 | 52047.06 | 63 |
| 20 | 56983.45 | 58 |
| 22 | 404931.47 | 55 |
| 24 | 61673.71 | 55 |
| 26 | 64370.41 | 51 |
| 28 | 56897.04 | 59 |
| 30 | 406850.06 | 52 |
| 32 | 56786.24 | 58 |
### Compiled Per Iteration
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2978.06 | 49 |
| 2 | 5965.09 | 47 |
| 4 | 11928.23 | 46 |
| 6 | 17892.58 | 45 |
| 8 | 23773.82 | 43 |
| 10 | 29705.61 | 42 |
| 12 | 35631.97 | 40 |
| 14 | 41563.35 | 34 |
| 16 | 47452.93 | 31 |
| 18 | 53505.42 | 27 |
| 20 | 59393.86 | 25 |
| 22 | 436115.28 | 23 |
| 24 | 71088.08 | 21 |
| 26 | 76928.70 | 19 |
| 28 | 82759.27 | 18 |
| 30 | 560658.97 | 17 |
| 32 | 93949.39 | 16 |
## Analysis
The C# compiled policy benchmark demonstrates important performance characteristics with mimalloc as the default allocator:
1. **Compilation Strategy Impact**: Shared compiled policies significantly outperform per-iteration compilation (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for shared policies
- Performance generally degrades with increased thread count, but mimalloc provides better allocation efficiency
3. **Performance Hierarchy**:
- Shared compiled policies: Best performance (optimal configuration)
- Per-iteration compilation: ~82% reduction from optimal
4. **Compilation Overhead**: Per-iteration compilation creates substantial overhead, similar to fresh engine creation
5. **Thread Contention**: Significant performance degradation beyond 8 threads for both configurations, though mimalloc helps mitigate some allocation-related issues
## Comparison with Rust Compiled Policy Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:-----------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Shared Policies | 273 / 426 | 227 / 342 | 126 / 185 |
| Per-iteration | 49 / 55 | 46 / 50 | 43 / 50 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:-----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Shared Policies | 249 / 384 | 150 / 203 | 58 / 123 |
| Per-iteration | 47 / 54 | 40 / 50 | 22 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 64% of Rust performance for shared policies, 89% for per-iteration
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Per-iteration compilation shows more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for shared policies
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Comparison with C# Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:----------------|:-------------------|:--------------------|:--------------------|
| | CP / EE | CP / EE | CP / EE |
| Shared Policies | 273 / 279 | 227 / 217 | 126 / 114 |
| Per-iteration | 49 / 50 | 46 / 47 | 43 / 45 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:----------------|:----------------------|:--------------------------|:-----------------------|
| | Avg CP / EE | Avg CP / EE | Avg CP / EE |
| Shared Policies | 249 / 248 | 150 / 128 | 58 / 54 |
| Per-iteration | 47 / 48 | 40 / 39 | 22 / 27 |
**Key Observations:**
- **Single-threaded parity**: Both systems perform nearly identically at 1 thread
- **Threading behavior**: Compiled policies slightly outperform engine evaluation at higher thread counts for shared policies
- **Contention resistance**: Per-iteration configurations show very similar performance characteristics across all thread counts
- **Platform consistency**: Both C# implementations show similar scaling patterns and contention behavior
## Performance Insights
1. **C# vs Rust Performance**: C# compiled policies achieve 65% average performance of Rust for shared policies, 87% average for per-iteration across low contention scenarios
2. **Engine vs Compiled**: In C#, engine and compiled policy evaluation show very similar average performance (compiled policies achieve 100% of engine performance for shared policies, 98% for per-iteration)
3. **mimalloc Impact**: The use of mimalloc as the default allocator in the underlying Rust FFI provides better memory allocation efficiency and improved threading characteristics
4. **Threading Scaling**: Both C# configurations demonstrate similar contention patterns, with shared policies showing more pronounced degradation under high thread contention compared to per-iteration compilation

View File

@@ -1,118 +0,0 @@
# Engine Evaluation Benchmark Results (C#/.NET)
## Test Environment
- **Platform**: Apple Silicon (M-Series)
- **CPU**: 16 cores
- **Architecture**: ARM64 (aarch64-apple-darwin)
- **.NET Version**: 8.0
- **Allocator**: mimalloc (default allocator for Rust FFI)
- **Benchmark Framework**: Custom time-based benchmarking
- **Test Data**: 20,000 inputs per evaluation (distributed across threads)
- **Policy**: Complex authorization policy with nested rules
- **Warmup Duration**: 3 seconds per configuration
- **Evaluation Duration**: 3 seconds per configuration
## Benchmark Overview
The C# engine evaluation benchmark tests Regorus policy evaluation performance across multiple thread configurations (1-32 threads). It measures throughput (thousands of evaluations per second) for different combinations of engine reuse strategies.
## Configuration Combinations
1. **Cloned Engines**: Each thread uses its own cloned engine instance - optimal for performance
2. **Fresh Engines**: Each thread creates a new engine for each evaluation iteration
*Note: The C# implementation uses a simpler configuration model compared to Rust, which also varies input data handling (cloned vs fresh inputs). The C# benchmarks focus on engine reuse strategies with consistent input handling.*
## Performance Results
### Cloned Engines (Best Performance)
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2903.43 | 279 |
| 2 | 5808.35 | 227 |
| 4 | 11645.08 | 217 |
| 6 | 17469.69 | 207 |
| 8 | 23268.07 | 114 |
| 10 | 28996.14 | 104 |
| 12 | 34808.60 | 98 |
| 14 | 40703.21 | 72 |
| 16 | 46488.23 | 63 |
| 18 | 52078.52 | 56 |
| 20 | 57014.31 | 51 |
| 22 | 60482.22 | 47 |
| 24 | 62445.67 | 46 |
| 26 | 65128.74 | 45 |
| 28 | 58001.92 | 50 |
| 30 | 66154.78 | 42 |
| 32 | 64999.03 | 45 |
### Fresh Engines
| Threads | Total Evaluation Time (ms) | Throughput (Kelem/s) |
|--------:|---------------------------:|---------------------:|
| 1 | 2982.28 | 50 |
| 2 | 5962.62 | 48 |
| 4 | 11917.94 | 47 |
| 6 | 17874.77 | 46 |
| 8 | 23729.94 | 45 |
| 10 | 29635.17 | 42 |
| 12 | 35574.71 | 38 |
| 14 | 41482.61 | 34 |
| 16 | 47425.16 | 32 |
| 18 | 53248.87 | 29 |
| 20 | 58424.34 | 27 |
| 22 | 61302.24 | 26 |
| 24 | 67430.08 | 23 |
| 26 | 65226.79 | 24 |
| 28 | 73118.48 | 22 |
| 30 | 326472.94 | 23 |
| 32 | 63805.03 | 24 |
## Analysis
The C# benchmark results demonstrate important performance characteristics with mimalloc as the default allocator:
1. **Engine Reuse Impact**: Cloned engines significantly outperform fresh engines (~5.6x at 1 thread)
2. **Scaling Patterns with mimalloc**:
- Best throughput achieved at 1 thread for both configurations
- Performance degrades with increased thread count due to contention, but mimalloc provides better allocation efficiency
- Cloned engines show better relative scaling characteristics
3. **Performance Hierarchy**:
- Cloned engines: Best performance (optimal configuration)
- Fresh engines: ~82% reduction from optimal
4. **Thread Contention**: Significant performance drop beyond 8 threads, especially for fresh engines, though mimalloc helps mitigate some allocation-related issues
5. **C# vs Rust Performance**: C# shows ~66% of Rust performance for equivalent cloned engine configuration
## Comparison with Rust Engine Evaluation
### Multi-Thread Performance Comparison
| Configuration | 1 Thread (Kelem/s) | 4 Threads (Kelem/s) | 8 Threads (Kelem/s) |
|:---------------|:-------------------|:--------------------|:--------------------|
| | C# / Rust | C# / Rust | C# / Rust |
| Cloned Engines | 279 / 423 | 217 / 406 | 114 / 341 |
| Fresh Engines | 50 / 56 | 47 / 54 | 45 / 53 |
### Threading Efficiency Analysis
| Configuration | Low Contention (1-4t) | Medium Contention (6-12t) | High Contention (16+t) |
|:---------------|:----------------------|:--------------------------|:-----------------------|
| | Avg C# / Rust | Avg C# / Rust | Avg C# / Rust |
| Cloned Engines | 253 / 414 | 128 / 329 | 54 / 250 |
| Fresh Engines | 48 / 55 | 39 / 52 | 27 / 42 |
**Key Observations:**
- **Single-threaded performance**: C# achieves 66% of Rust performance for cloned engines, 89% for fresh engines
- **Threading scaling**: Both platforms show similar degradation patterns, but Rust maintains better absolute performance
- **Contention resistance**: Fresh engines show more consistent relative performance across thread counts
- **Platform differences**: C# shows more pronounced performance drops at higher thread counts, particularly for cloned engines
*Note: Rust benchmarks include additional input data variations (cloned vs fresh inputs) that are not present in the C# implementation.*
## Performance Insights
1. **Engine Creation Overhead**: Fresh engine creation has significant performance impact in C# (~5.6x slower than cloned engines)
2. **Thread Scaling**: C# shows moderate thread contention with better characteristics when using mimalloc
3. **Memory Management**: .NET garbage collection patterns combined with mimalloc allocation efficiency
4. **Interop Performance**: C# bindings achieve 66% of Rust performance for cloned engines, demonstrating effective FFI implementation
5. **mimalloc Benefits**: The use of mimalloc as the default allocator in the underlying Rust FFI provides improved memory allocation efficiency and better threading characteristics

View File

@@ -1,15 +0,0 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RegorusPackageVersion>0.9.1</RegorusPackageVersion>
<RegorusPackageVersionSuffix Condition="'$(VersionSuffix)' != ''">-$(VersionSuffix)</RegorusPackageVersionSuffix>
</PropertyGroup>
<ItemGroup>
<!-- Centralize Regorus package version with optional CI suffix -->
<PackageVersion Include="Regorus" Version="$(RegorusPackageVersion)$(RegorusPackageVersionSuffix)" />
<PackageVersion Include="MSTest" Version="3.8.2" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="YamlDotNet" Version="13.7.0" />
</ItemGroup>
</Project>

View File

@@ -1,152 +0,0 @@
# Regorus CSharp
**Regorus** is
- *Rego*-*Rus(t)* - A fast, light-weight [Rego](https://www.openpolicyagent.org/docs/latest/policy-language/)
interpreter written in Rust.
- *Rigorous* - A rigorous enforcer of well-defined Rego semantics.
See main [Regorus page](https://github.com/microsoft/regorus) for more details about the project.
# Building
## Github Actions
The simplest way to build a Nuget for Regorus' C# bindings is to use Github Actions. The action to do so is named `bindings/csharp` and is defined in `.github/workflows/test-csharp.yml`.
There are two ways to trigger a Nuget build.
1. Runs are triggered automatically whenever a push or pull request is made to the `main` branch.
2. A run can be triggered manually by navigating to the action in the Github UI and clicking `Run workflow`. This option allows you to generate a Nuget for any branch, which is useful when testing the integration of in-progress changes to Regorus with other projects. Nuget files that are generated via this flow will have a `manualtrigger` suffix appended to their version number, making it easy to distinguish them from Nugets generated using the `main` branch.
![Image displaying the run workflow button](docs/images/readme/manuallytriggeringrun.png)
Once the workflow run completes, the generated Nuget can be downloaded by following these steps:
1. Open the run.
2. Click on `Build Regorus nuget` on the left.
3. Expand the `Upload Regorus nuget` step.
4. Click the `Artifact download URL` link at the bottom.
5. Save and extract the downloaded zip file to find the `.nupkg` file.
![Image displaying the download URL link](docs/images/readme/downloadnuget.png)
## Local
The `cargo xtask` runner provides helpers for local builds:
1. `cargo xtask ffi` builds the `bindings/ffi` crate for the host platform in debug mode. Add `--target <triple>` (repeatable) to cross-compile, or `--release` to produce optimised artefacts. Results land under `bindings/ffi/target/<triple>/<profile>`.
2. `cargo xtask nuget` reuses those artefacts to pack the C# library. It defaults to debug builds for the host but accepts `--target`, `--release`, `--artifacts-dir <path>` to reuse existing binaries, and `--enforce-artifacts` to require every officially supported platform.
3. `cargo xtask test-csharp` ensures a NuGet is available (rebuilding when required or when `--force-nuget` is passed) and then runs `Regorus.Tests`, `TestApp`, and `TargetExampleApp` against it. The command accepts the same build flags as `cargo xtask nuget`.
## Memory Usage Safeguards
The C# bindings expose allocator-backed memory tracking utilities via the static `Regorus.MemoryLimits` helper. Typical usage:
```csharp
// Restrict total allocations to 128 MiB for the process
Regorus.MemoryLimits.SetGlobalMemoryLimit(128 * 1024 * 1024);
// Optional: tune how frequently each thread flushes its allocation counters
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(256 * 1024);
// Engine operations throw InvalidOperationException with the allocator message if the budget is exceeded
using var engine = new Regorus.Engine();
var veryLargeJson = new string('x', 128 * 1024);
try
{
engine.SetInputJson(veryLargeJson);
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Allocator reported: {ex.Message}");
}
// Restore defaults once done
Regorus.MemoryLimits.SetGlobalMemoryLimit(null);
Regorus.MemoryLimits.SetThreadFlushThresholdOverride(null);
```
See bindings/csharp/Regorus.Tests/RegorusTests.cs for scenario coverage and bindings/csharp/TargetExampleApp/Program.cs for end-to-end usage.
## RVM Usage Example
The RVM API lets you compile a program from modules/entrypoints and execute it in a VM:
```csharp
using Regorus;
const string Policy = """
package demo
default allow = false
allow if {
input.user == "alice"
some role in data.roles[input.user]
role == "admin"
}
""";
const string Data = """
{ "roles": { "alice": ["admin"] } }
""";
const string Input = """
{ "user": "alice" }
""";
var modules = new[] { new PolicyModule("demo.rego", Policy) };
var entryPoints = new[] { "data.demo.allow" };
using var program = Program.CompileFromModules(Data, modules, entryPoints);
var listing = program.GenerateListing();
using var vm = new Rvm();
vm.LoadProgram(program);
vm.SetDataJson(Data);
vm.SetInputJson(Input);
var result = vm.Execute();
Console.WriteLine($"allow: {result}");
```
## Azure RBAC Condition Evaluation
Evaluate Azure RBAC condition expressions directly with a JSON evaluation context:
```csharp
using Regorus;
const string Condition = "@Resource[owner] StringEquals 'alice'";
const string ContextJson = """
{
"principal": {
"id": "user-1",
"principal_type": "User",
"custom_security_attributes": {}
},
"resource": {
"id": "/subscriptions/s1",
"resource_type": "Microsoft.Storage/storageAccounts",
"scope": "/subscriptions/s1",
"attributes": {
"owner": "alice",
"confidential": true
}
},
"request": {
"action": "Microsoft.Storage/storageAccounts/read",
"data_action": null,
"attributes": {
"clientIP": "10.0.0.1"
}
},
"environment": {
"is_private_link": null,
"private_endpoint": null,
"subnet": null,
"utc_now": "2023-05-01T12:00:00Z"
},
"action": "Microsoft.Storage/storageAccounts/read",
"suboperation": null
}
""";
var allowed = RbacEngine.EvaluateCondition(Condition, ContextJson);
Console.WriteLine($"RBAC condition allowed: {allowed}");
```

View File

@@ -1,191 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
public class AliasRegistryTests
{
private const string AliasesJson = @"[{
""namespace"": ""Microsoft.Storage"",
""resourceTypes"": [{
""resourceType"": ""storageAccounts"",
""aliases"": [{
""name"": ""Microsoft.Storage/storageAccounts/supportsHttpsTrafficOnly"",
""defaultPath"": ""properties.supportsHttpsTrafficOnly"",
""paths"": []
}, {
""name"": ""Microsoft.Storage/storageAccounts/accessTier"",
""defaultPath"": ""properties.accessTier"",
""paths"": []
}]
}]
}]";
private const string ManifestJson = @"{
""dataNamespace"": ""Microsoft.KeyVault.Data"",
""aliases"": [],
""resourceTypeAliases"": [{
""resourceType"": ""vaults/certificates"",
""aliases"": [{
""name"": ""Microsoft.KeyVault.Data/vaults/certificates/keySize"",
""paths"": [{ ""path"": ""keySize"", ""apiVersions"": [""7.0""] }]
}]
}]
}";
[TestMethod]
public void Create_and_dispose_succeeds()
{
using var registry = new AliasRegistry();
Assert.AreEqual(0, registry.Length);
}
[TestMethod]
public void LoadJson_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void LoadManifest_populates_registry()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
Assert.AreEqual(1, registry.Length);
}
[TestMethod]
public void NormalizeAndWrap_produces_envelope()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
}";
var result = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.IsNotNull(envelope["resource"]);
Assert.IsNotNull(envelope["parameters"]);
Assert.IsNotNull(envelope["context"]);
// Normalized resource should have lowercased alias field names
var res = envelope["resource"]!;
Assert.AreEqual(true, res["supportshttpstrafficonly"]?.GetValue<bool>());
Assert.AreEqual("Hot", res["accesstier"]?.GetValue<string>());
Assert.AreEqual("acct1", res["name"]?.GetValue<string>());
}
[TestMethod]
public void NormalizeAndWrap_with_context_and_parameters()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true }
}";
var context = @"{""resourceGroup"": {""name"": ""rg1""}}";
var parameters = @"{""env"": ""prod""}";
var result = registry.NormalizeAndWrap(resource, "2023-01-01", context, parameters);
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.AreEqual("rg1", envelope["context"]!["resourceGroup"]!["name"]?.GetValue<string>());
Assert.AreEqual("prod", envelope["parameters"]!["env"]?.GetValue<string>());
}
[TestMethod]
public void Denormalize_restores_properties()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var normalized = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""supportshttpstrafficonly"": true,
""accesstier"": ""Hot""
}";
var result = registry.Denormalize(normalized, "2023-01-01");
Assert.IsNotNull(result);
var arm = JsonNode.Parse(result!)!;
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
}
[TestMethod]
public void Round_trip_normalize_then_denormalize()
{
using var registry = new AliasRegistry();
registry.LoadJson(AliasesJson);
var resource = @"{
""name"": ""acct1"",
""type"": ""Microsoft.Storage/storageAccounts"",
""properties"": { ""supportsHttpsTrafficOnly"": true, ""accessTier"": ""Hot"" }
}";
// Normalize
var envelopeJson = registry.NormalizeAndWrap(resource, "2023-01-01", "{}", "{}");
Assert.IsNotNull(envelopeJson);
var envelope = JsonNode.Parse(envelopeJson!)!;
var normalizedResource = envelope["resource"]!.ToJsonString();
// Denormalize
var armJson = registry.Denormalize(normalizedResource, "2023-01-01");
Assert.IsNotNull(armJson);
var arm = JsonNode.Parse(armJson!)!;
Assert.AreEqual(true, arm["properties"]!["supportsHttpsTrafficOnly"]?.GetValue<bool>());
Assert.AreEqual("Hot", arm["properties"]!["accessTier"]?.GetValue<string>());
Assert.AreEqual("acct1", arm["name"]?.GetValue<string>());
}
[TestMethod]
public void DataPlane_manifest_normalize()
{
using var registry = new AliasRegistry();
registry.LoadManifest(ManifestJson);
var resource = @"{
""type"": ""Microsoft.KeyVault.Data/vaults/certificates"",
""keySize"": 2048
}";
var result = registry.NormalizeAndWrap(resource, "7.0", "{}", "{}");
Assert.IsNotNull(result);
var envelope = JsonNode.Parse(result!)!;
Assert.AreEqual(2048, envelope["resource"]!["keysize"]?.GetValue<int>());
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void LoadJson_invalid_throws()
{
using var registry = new AliasRegistry();
registry.LoadJson("not valid json");
}
}

View File

@@ -1,131 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
using System.Text.Json;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[DoNotParallelize] // Uses global fallback config; must run sequentially.
[TestClass]
public class ExecutionTimerTests
{
private const string Policy = @"
package limits.timer
import rego.v1
triplet_count := count([1 |
x := data.values[_]
y := data.values[_]
z := data.values[_]
])
";
private const string Query = "data.limits.timer.triplet_count";
private const int ValueCount = 160;
[TestMethod]
public void Engine_limit_enforced()
{
Engine.ClearFallbackExecutionTimerConfig();
using var engine = CreateEngine(ValueCount);
var config = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
engine.SetExecutionTimerConfig(config);
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
StringAssert.Contains(ex.Message, "execution exceeded time limit");
}
[TestMethod]
public void Fallback_applies_to_new_engines()
{
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
Engine.SetFallbackExecutionTimerConfig(fallback);
try
{
using var engine = CreateEngine(ValueCount);
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
StringAssert.Contains(ex.Message, "execution exceeded time limit");
}
finally
{
Engine.ClearFallbackExecutionTimerConfig();
}
}
[TestMethod]
public void Engine_override_relaxes_fallback()
{
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
Engine.SetFallbackExecutionTimerConfig(fallback);
try
{
using var engine = CreateEngine(ValueCount);
var relaxed = new ExecutionTimerConfig(TimeSpan.FromSeconds(12), checkInterval: 1);
engine.SetExecutionTimerConfig(relaxed);
var resultJson = engine.EvalRule(Query);
var result = JsonSerializer.Deserialize<int>(resultJson!);
Assert.IsTrue(result > 0, "Expected a positive triplet count when limit is relaxed.");
engine.ClearExecutionTimerConfig();
var ex = Assert.ThrowsException<InvalidOperationException>(() => engine.EvalRule(Query));
StringAssert.Contains(ex.Message, "execution exceeded time limit");
}
finally
{
Engine.ClearFallbackExecutionTimerConfig();
}
}
[TestMethod]
public void CompiledPolicy_limit_enforced()
{
var fallback = new ExecutionTimerConfig(TimeSpan.FromMilliseconds(2), checkInterval: 1);
Engine.SetFallbackExecutionTimerConfig(fallback);
try
{
using var policy = CreateCompiledPolicy(ValueCount);
var ex = Assert.ThrowsException<InvalidOperationException>(() => policy.EvalWithInput("null"));
StringAssert.Contains(ex.Message, "execution exceeded time limit");
}
finally
{
Engine.ClearFallbackExecutionTimerConfig();
}
}
[TestMethod]
public void CompiledPolicy_uses_engine_limits_only()
{
// Compiled policies no longer store per-policy execution timers; limits are managed by Engine.
Engine.ClearFallbackExecutionTimerConfig();
using var policy = CreateCompiledPolicy(ValueCount);
var resultJson = policy.EvalWithInput("null");
var result = JsonSerializer.Deserialize<int>(resultJson!);
Assert.IsTrue(result > 0, "CompiledPolicy should evaluate using engine defaults without its own timer");
}
private static Engine CreateEngine(int valueCount)
{
var engine = new Engine();
engine.AddPolicy("limits_timer.rego", Policy);
engine.AddDataJson(CreateData(valueCount));
return engine;
}
private static CompiledPolicy CreateCompiledPolicy(int valueCount)
{
var modules = new[] { new PolicyModule("limits_timer.rego", Policy) };
return Compiler.CompilePolicyWithEntrypoint(CreateData(valueCount), modules, Query);
}
private static string CreateData(int valueCount)
{
var payload = new { values = Enumerable.Range(0, valueCount).ToArray() };
return JsonSerializer.Serialize(payload);
}
}

View File

@@ -1,320 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Regorus;
namespace Regorus.Tests;
[TestClass]
[DoNotParallelize]
public class MemoryGrowthTests
{
private static int Iterations =>
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_ITERS"), out var value) ? value : 50_000;
private static int LogEvery =>
int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_LOG_EVERY"), out var value) ? value : 500;
private static int GcEvery
{
get
{
if (!int.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GC_EVERY"), out var value))
{
value = LogEvery;
}
return value <= 0 ? LogEvery : value;
}
}
private static long? MaxWorkingSetDeltaBytes
{
get
{
if (!long.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_MAX_DELTA_MB"), out var mb))
{
mb = 32;
}
if (mb <= 0)
{
return null;
}
return mb * 1024L * 1024L;
}
}
private static ulong? GlobalRegorusMemoryLimitBytes
{
get
{
if (!ulong.TryParse(Environment.GetEnvironmentVariable("REGORUS_MEMORY_TEST_GLOBAL_REGORUS_LIMIT_MB"), out var mb))
{
return null;
}
if (mb == 0)
{
return null;
}
return mb * 1024UL * 1024UL;
}
}
private static void WithOptionalGlobalRegorusMemoryLimit(Action action)
{
var priorLimit = MemoryLimits.GetGlobalMemoryLimit();
try
{
if (GlobalRegorusMemoryLimitBytes is { } limit)
{
MemoryLimits.SetGlobalMemoryLimit(limit);
}
action();
}
finally
{
MemoryLimits.SetGlobalMemoryLimit(priorLimit);
}
}
private static void ForceFullGc()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
[TestMethod]
public void Engine_create_eval_dispose_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
using (var engine = new Engine())
{
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
_ = engine.EvalRule("data.test.message");
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Engine_create_eval_finalize_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
var engine = new Engine();
engine.AddPolicy("test.rego", "package test\nx = 1\nmessage = `Hello`");
_ = engine.EvalRule("data.test.message");
if (i % GcEvery == 0)
{
ForceFullGc();
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Rvm_rehydrate_execute_dispose_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var modules = new[]
{
new PolicyModule("test.rego", "package test\nallow = true"),
};
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
var serialized = compiled.SerializeBinary();
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
using (var vm = new Rvm())
using (var program = Program.DeserializeBinary(serialized, out _))
{
vm.LoadProgram(program);
vm.SetDataJson("{}");
vm.SetInputJson("{}");
_ = vm.ExecuteEntryPoint(0);
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
[TestMethod]
public void Rvm_rehydrate_execute_finalize_does_not_grow_working_set()
{
WithOptionalGlobalRegorusMemoryLimit(() =>
{
var modules = new[]
{
new PolicyModule("test.rego", "package test\nallow = true"),
};
using var compiled = Program.CompileFromModules("{}", modules, new[] { "data.test.allow" });
var serialized = compiled.SerializeBinary();
var process = Process.GetCurrentProcess();
process.Refresh();
var baseline = process.WorkingSet64;
var maxDelta = 0L;
var baselineManaged = GC.GetTotalMemory(false);
var maxManagedDelta = 0L;
for (var i = 1; i <= Iterations; i++)
{
var vm = new Rvm();
var program = Program.DeserializeBinary(serialized, out _);
vm.LoadProgram(program);
vm.SetDataJson("{}");
vm.SetInputJson("{}");
_ = vm.ExecuteEntryPoint(0);
if (i % GcEvery == 0)
{
ForceFullGc();
}
if (i % LogEvery == 0)
{
process.Refresh();
var workingSet = process.WorkingSet64;
var managed = GC.GetTotalMemory(false);
var delta = workingSet - baseline;
var managedDelta = managed - baselineManaged;
if (delta > maxDelta)
{
maxDelta = delta;
}
if (managedDelta > maxManagedDelta)
{
maxManagedDelta = managedDelta;
}
Console.WriteLine($"\n\n\u001b[1m{i} ws_mb={workingSet / 1048576.0:F1} managed_mb={managed / 1048576.0:F1} delta_mb={delta / 1048576.0:F1}\u001b[0m\n\n");
}
}
if (MaxWorkingSetDeltaBytes is { } limit)
{
Console.WriteLine($"\n\n\u001b[1mSUMMARY: max ws delta {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB); max managed delta {maxManagedDelta / 1048576.0:F1} MB.\u001b[0m\n\n");
Assert.IsTrue(
maxDelta <= limit,
$"Working set grew by {maxDelta / 1048576.0:F1} MB (limit {limit / 1048576.0:F1} MB). Managed heap max delta {maxManagedDelta / 1048576.0:F1} MB.");
}
});
}
}

Some files were not shown because too many files have changed in this diff Show More