mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
feat!: add Rego Virtual Machine (RVM) implementation (#495)
* feat!: add Rego Virtual Machine (RVM) implementation This commit introduces a register-based virtual machine for executing Rego policies with bytecode-style instructions. Unlike the existing tree-walking interpreter, the RVM compiles policies into instruction sequences that operate on virtual registers, offering better performance and optimization potential. Core Components: Instruction Set Architecture: - Define instruction types for data operations, control flow, and builtins - Implement instruction parameter encoding and display formatting - Add instruction parser with comprehensive test coverage Virtual Machine Engine: - Register-based execution model with program counter management - Loop execution supporting iterators, comprehensions, and quantifiers - Function call handling with argument evaluation and context management - Rule evaluation with default value resolution and virtual data support - Arithmetic and comparison operation implementations Program Representation: - Program listing builder with instruction sequencing - Rule tree construction for organizing policy rules - Binary and JSON serialization for compiled programs - Recompilation support for program modification Testing Infrastructure: - Extensive YAML test suites covering all VM features - Rust unit tests for VM execution and instruction parsing - Test suites for loops, comprehensions, builtins, and control flow BREAKING CHANGE: Introduces new VM execution path alongside interpreter Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> * docs: add detailed RVM architecture references Introduce architecture.md explaining program artifacts, serialization, and runtime subsystems. Document the full opcode catalog in instruction-set.md, including operands, parameter tables, and outcomes. Walk through execution flow, stacks, and operational guidance in vm-runtime.md, tying the runtime to the new architecture docs. Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com> --------- Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
6dc505c88b
commit
49bd3c22f3
78
tests/rvm/vm/README.md
Normal file
78
tests/rvm/vm/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Regorus VM Test Suites
|
||||
|
||||
This directory hosts YAML-driven regression suites for the Regorus virtual machine. Each YAML file is converted into parameterised Rust tests by `src/rvm/tests/vm.rs`, so the contents here define the end-to-end VM coverage.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Enable the `rvm` feature (it pulls in `std` and the VM runtime) whenever you run these tests:
|
||||
- `cargo test --features rvm run_vm_test_file`
|
||||
- `cargo test --features rvm run_loop_test_file`
|
||||
- Append `-- --nocapture` to surface per-test diagnostics when a failure occurs.
|
||||
- Individual generated tests follow the pattern `run_vm_test_file_tests_rvm_vm_suites_<suite>_yaml`, so you can use that fragment with `cargo test` to run a single suite.
|
||||
|
||||
## Layout
|
||||
|
||||
- `suites/*.yaml` — primary instruction, control-flow, and integration suites.
|
||||
- `suites/loops/*.yaml` — dedicated loop/comprehension suites.
|
||||
- Mirror the comment headers inside each suite when adding new files; the descriptions are surfaced in this README for quick reference.
|
||||
|
||||
## Main Suites (`suites/*.yaml`)
|
||||
|
||||
| Suite | Focus |
|
||||
| --- | --- |
|
||||
| `arithmetic_operations.yaml` | Arithmetic opcodes (`Add`, `Sub`, `Mul`, `Div`, `Mod`) and simple expressions. |
|
||||
| `assertions.yaml` | `AssertCondition` semantics, including success/failure and loop control interactions. |
|
||||
| `basic_instructions.yaml` | Core load/move/return instructions that underpin every program. |
|
||||
| `boolean_literals.yaml` | `LoadBool`, `LoadTrue`, `LoadFalse`, and their interaction with logical operators. |
|
||||
| `builtin_functions.yaml` | Builtin dispatch covering argument marshalling, return handling, and error cases. |
|
||||
| `call_rule.yaml` | `CallRule` execution, rule caches, defaults, and fallbacks. |
|
||||
| `comparison_operations.yaml` | Relational operators plus logical combining (`Eq`, `Ne`, `Lt`, `Le`, `Gt`, `Ge`, `And`, `Or`, `Not`). |
|
||||
| `complex.yaml` | Deeply nested hybrid loops, comprehensions, and rule calls that stress the scheduler. |
|
||||
| `constructed_collections.yaml` | `ArrayCreate`/`SetCreate` success paths, undefined propagation, and deduplication. |
|
||||
| `control_flow.yaml` | Conditional branching patterns, nested assertions, and selection logic. |
|
||||
| `core_semantics.yaml` | Broad regression coverage for arithmetic, comparisons, loops, assertions, and collection helpers. |
|
||||
| `data_structures.yaml` | Array/object/set creation, access, and mutation instructions. |
|
||||
| `deep_nesting.yaml` | Three-plus levels of mixed loop modes validating register pressure and control flow correctness. |
|
||||
| `default_rules.yaml` | Complete rule execution with default literals and failure fallbacks. |
|
||||
| `destructuring_rules.yaml` | Destructuring metadata handling, success/early-exit semantics. |
|
||||
| `function_calls.yaml` | User function invocation plumbing, argument passing, and returns. |
|
||||
| `halt.yaml` | `Halt` instruction returning register `0` and stopping execution. |
|
||||
| `host_await.yaml` | Successful `HostAwait` responses across execution modes and run-to-completion flows. |
|
||||
| `host_await_failures.yaml` | Error signalling and ignore-flag behaviour for `HostAwait`. |
|
||||
| `indexed_access.yaml` | Literal/register indexing, chained accesses, and undefined propagation. |
|
||||
| `integration_scenarios.yaml` | Real-world policy shapes (RBAC, filtering, transforms, workflows). |
|
||||
| `interpreter_operator_compatibility.yaml` | Ensures VM operators match interpreter behaviour on edge cases. |
|
||||
| `invalid_collection_ops.yaml` | Error paths for object/set/array mutations with incorrect types. |
|
||||
| `load_data_input.yaml` | `LoadData` and `LoadInput` instructions across nested/empty/undefined sources. |
|
||||
| `loop_invalid_iteration.yaml` | Loop errors for non-iterables plus instruction-limit enforcement. |
|
||||
| `null_undefined_handling.yaml` | Null/undefined behaviour across arithmetic, comparisons, indexing, loops, and comprehensions. |
|
||||
| `object_operations.yaml` | Advanced object templates, dynamic keys, collisions, and validation. |
|
||||
| `predefined.yaml` | Global `data` and `input` bindings, including nested access patterns. |
|
||||
| `resource_limits.yaml` | Instruction counts, recursion depth, and other resource exhaustion scenarios. |
|
||||
| `serialization.yaml` | Round-trip binary serialization for compiled programs covering all instruction families. |
|
||||
| `set_operations.yaml` | Set creation, deduplication, membership checks, and nested values. |
|
||||
| `type_errors.yaml` | Graceful error reporting for cross-family type mismatches. |
|
||||
| `virtual_data_lookup.yaml` | `VirtualDataDocumentLookup` with base data, rule overrides, and invalid indices. |
|
||||
|
||||
## Loop Suites (`suites/loops/*.yaml`)
|
||||
|
||||
| Suite | Focus |
|
||||
| --- | --- |
|
||||
| `array_comprehensions.yaml` | Mapping, filtering, and edge cases for array comprehensions. |
|
||||
| `empty.yaml` | Behaviour of every loop mode over empty collections (vacuous truth/falsehood). |
|
||||
| `existential.yaml` | `some`-style (`Any`) quantification including early exits and complex predicates. |
|
||||
| `loop_comprehension_interactions.yaml` | Interplay between nested loops and comprehensions emitting structured data. |
|
||||
| `nested.yaml` | Mixed nesting patterns for loops and comprehensions with varying depth. |
|
||||
| `nested_fixed.yaml` | Placeholder for future fixed-nesting scenarios (no cases yet). |
|
||||
| `object_comprehensions.yaml` | Key/value emission, collision handling, and filtering in object comprehensions. |
|
||||
| `set_comprehensions.yaml` | Deduplication and uniqueness guarantees in set comprehensions. |
|
||||
| `universal.yaml` | `every`-style (`Every`) quantification, early failure, and vacuous truth cases. |
|
||||
|
||||
## Adding or Updating Suites
|
||||
|
||||
1. Place the new YAML file under `suites/` (or the relevant `suites/loops/` subdirectory).
|
||||
2. Add a concise comment block at the top describing the intent and scenarios.
|
||||
3. Update the tables above so the catalog stays accurate.
|
||||
4. Run `cargo test --features rvm run_vm_test_file` to ensure the suite loads and all cases pass.
|
||||
|
||||
Keeping this README current makes it easier to discover coverage gaps and reason about the generated tests.
|
||||
168
tests/rvm/vm/suites/arithmetic_operations.yaml
Normal file
168
tests/rvm/vm/suites/arithmetic_operations.yaml
Normal file
@@ -0,0 +1,168 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Arithmetic Operations Test Suite
|
||||
# Tests mathematical operations: Add, Sub, Mul, Div
|
||||
# These instructions perform basic arithmetic on numeric values
|
||||
|
||||
cases:
|
||||
- note: arithmetic_add
|
||||
description: Test Add instruction
|
||||
example_rego: "10 + 5" # Addition expression
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
|
||||
- "Add { dest: 2, left: 0, right: 1 }" # Add register 0 + register 1, store in register 2
|
||||
- "Return { value: 2 }" # Return result from register 2
|
||||
want_result: 15
|
||||
|
||||
- note: arithmetic_sub
|
||||
description: Test Sub instruction
|
||||
example_rego: "10 - 3" # Subtraction expression
|
||||
literals:
|
||||
- 10
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
|
||||
- "Sub { dest: 2, left: 0, right: 1 }" # Subtract register 1 from register 0, store in register 2
|
||||
- "Return { value: 2 }" # Return result from register 2
|
||||
want_result: 7
|
||||
|
||||
- note: arithmetic_mul
|
||||
description: Test Mul instruction
|
||||
example_rego: "4 * 6" # Multiplication expression
|
||||
literals:
|
||||
- 4
|
||||
- 6
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 4 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 6 into register 1
|
||||
- "Mul { dest: 2, left: 0, right: 1 }" # Multiply register 0 * register 1, store in register 2
|
||||
- "Return { value: 2 }" # Return result from register 2
|
||||
want_result: 24
|
||||
|
||||
- note: arithmetic_div
|
||||
description: Test Div instruction
|
||||
example_rego: "15 / 3" # Division expression
|
||||
literals:
|
||||
- 15
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 15 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
|
||||
- "Div { dest: 2, left: 0, right: 1 }" # Divide register 0 / register 1, store in register 2
|
||||
- "Return { value: 2 }" # Return result from register 2
|
||||
want_result: 5
|
||||
|
||||
- note: arithmetic_div_by_zero
|
||||
description: Test Div by zero - undefined normally, error in strict mode
|
||||
example_rego: "10 / 0"
|
||||
literals:
|
||||
- 10
|
||||
- 0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Div { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
want_error_strict: "Cannot divide"
|
||||
|
||||
- note: arithmetic_mod
|
||||
description: Test Mod instruction
|
||||
example_rego: "10 % 3"
|
||||
literals:
|
||||
- 10
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 1
|
||||
|
||||
- note: arithmetic_mod_by_zero
|
||||
description: Test Mod by zero - undefined normally, error in strict mode
|
||||
example_rego: "10 % 0"
|
||||
literals:
|
||||
- 10
|
||||
- 0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
want_error_strict: "Cannot modulo"
|
||||
|
||||
- note: arithmetic_mod_negative_operands
|
||||
description: Test Mod with negative operands
|
||||
example_rego: "-10 % 3"
|
||||
literals:
|
||||
- -10
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: -1
|
||||
|
||||
- note: arithmetic_mod_on_float_error
|
||||
description: Test Mod on float - should error
|
||||
example_rego: "10.5 % 3"
|
||||
literals:
|
||||
- 10.5
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "modulo on floating-point number"
|
||||
|
||||
- note: arithmetic_chained_operations
|
||||
description: Test chained arithmetic operations (a + b) * c
|
||||
example_rego: "(5 + 3) * 2"
|
||||
literals:
|
||||
- 5
|
||||
- 3
|
||||
- 2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 5
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 3
|
||||
- "Add { dest: 2, left: 0, right: 1 }" # 5 + 3 = 8
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 2
|
||||
- "Mul { dest: 4, left: 2, right: 3 }" # 8 * 2 = 16
|
||||
- "Return { value: 4 }"
|
||||
want_result: 16
|
||||
|
||||
- note: arithmetic_float_precision
|
||||
description: Test float arithmetic precision
|
||||
example_rego: "0.1 + 0.2"
|
||||
literals:
|
||||
- 0.1
|
||||
- 0.2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 0.3
|
||||
|
||||
- note: arithmetic_large_numbers
|
||||
description: Test arithmetic with large numbers
|
||||
example_rego: "1000000 * 1000000"
|
||||
literals:
|
||||
- 1000000
|
||||
- 1000000
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mul { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 1000000000000
|
||||
47
tests/rvm/vm/suites/assertions.yaml
Normal file
47
tests/rvm/vm/suites/assertions.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
# Assertions Test Suite
|
||||
# Exercises AssertNotUndefined behavior in isolation and inside loops.
|
||||
|
||||
cases:
|
||||
- note: assert_not_undefined_passes
|
||||
description: AssertNotUndefined succeeds when register has a value
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "AssertNotUndefined { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: 42
|
||||
|
||||
- note: assert_not_undefined_fails
|
||||
description: AssertNotUndefined triggers assertion error when register undefined
|
||||
literals:
|
||||
- "#undefined"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "AssertNotUndefined { register: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_error: "Assertion failed"
|
||||
|
||||
- note: assert_not_undefined_inside_loop
|
||||
description: AssertNotUndefined fails inside Every loop and exits with false result
|
||||
literals:
|
||||
- "#undefined"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 3
|
||||
body_start: 5
|
||||
loop_end: 7
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "AssertNotUndefined { register: 2 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 7 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: false
|
||||
32
tests/rvm/vm/suites/basic_instructions.yaml
Normal file
32
tests/rvm/vm/suites/basic_instructions.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Basic VM Instructions Test Suite
|
||||
# Tests fundamental instructions: Load, Move, Return
|
||||
# These form the foundation for all other VM operations
|
||||
|
||||
cases:
|
||||
- note: load_instruction
|
||||
description: Test basic Load instruction
|
||||
example_rego: "42" # Simple literal expression
|
||||
literals:
|
||||
- 42
|
||||
- "hello"
|
||||
- true
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 42 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal "hello" into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load literal true into register 2
|
||||
- "Return { value: 0 }" # Return value from register 0
|
||||
want_result: 42
|
||||
|
||||
- note: move_instruction
|
||||
description: Test Move instruction
|
||||
example_rego: "x := 123; x" # Variable assignment and reference
|
||||
literals:
|
||||
- 123
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 123 into register 0 (x := 123)
|
||||
- "Move { dest: 1, src: 0 }" # Move value from register 0 to register 1 (reference x)
|
||||
- "Return { value: 1 }" # Return value from register 1
|
||||
want_result: 123
|
||||
44
tests/rvm/vm/suites/boolean_literals.yaml
Normal file
44
tests/rvm/vm/suites/boolean_literals.yaml
Normal file
@@ -0,0 +1,44 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Boolean Literal Instructions Test Suite
|
||||
# Validates that LoadBool behaves consistently with LoadTrue/LoadFalse
|
||||
# and integrates correctly with logical instructions.
|
||||
|
||||
cases:
|
||||
- note: load_bool_true
|
||||
description: LoadBool true matches LoadTrue semantics
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadBool { dest: 0, value: true }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_bool_false
|
||||
description: LoadBool false matches LoadFalse semantics
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadBool { dest: 0, value: false }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: false
|
||||
|
||||
- note: load_bool_with_and
|
||||
description: Combine LoadBool values using And instruction
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadBool { dest: 0, value: true }"
|
||||
- "LoadBool { dest: 1, value: false }"
|
||||
- "And { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
- note: load_bool_with_or_not
|
||||
description: Ensure LoadBool integrates with Or and Not
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadBool { dest: 0, value: false }"
|
||||
- "LoadBool { dest: 1, value: true }"
|
||||
- "Or { dest: 2, left: 0, right: 1 }"
|
||||
- "Not { dest: 3, operand: 2 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: false
|
||||
524
tests/rvm/vm/suites/builtin_functions.yaml
Normal file
524
tests/rvm/vm/suites/builtin_functions.yaml
Normal file
@@ -0,0 +1,524 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Builtin Functions Test Suite
|
||||
# Tests the VM's builtin function call mechanism with various builtin functions
|
||||
# Covers argument handling, return values, and error cases
|
||||
|
||||
cases:
|
||||
# Basic Count Function Tests
|
||||
- note: builtin_count_array
|
||||
description: Test count builtin with array argument
|
||||
example_rego: "count([1, 2, 3])"
|
||||
literals:
|
||||
- [1, 2, 3]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "count"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 2, 3] into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 3
|
||||
|
||||
- note: builtin_count_string
|
||||
description: Test count builtin with string argument
|
||||
example_rego: "count(\"hello\")"
|
||||
literals:
|
||||
- "hello"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "count"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 5
|
||||
|
||||
- note: builtin_count_object
|
||||
description: Test count builtin with object argument
|
||||
example_rego: "count({\"a\": 1, \"b\": 2})"
|
||||
literals:
|
||||
- {"a": 1, "b": 2}
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "count"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load object into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 2
|
||||
|
||||
- note: builtin_count_empty_array
|
||||
description: Test count builtin with empty array
|
||||
example_rego: "count([])"
|
||||
literals:
|
||||
- []
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "count"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call count(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 0
|
||||
|
||||
# Max Function Tests
|
||||
- note: builtin_max_array
|
||||
description: Test max builtin with array argument
|
||||
example_rego: "max([1, 5, 3])"
|
||||
literals:
|
||||
- [1, 5, 3]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "max"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 5, 3] into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call max(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 5
|
||||
|
||||
- note: builtin_max_empty_array
|
||||
description: Test max builtin with empty array returns undefined
|
||||
example_rego: "max([])"
|
||||
literals:
|
||||
- []
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "max"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call max(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "#undefined"
|
||||
|
||||
# Min Function Tests
|
||||
- note: builtin_min_array
|
||||
description: Test min builtin with array argument
|
||||
example_rego: "min([1, 5, 3])"
|
||||
literals:
|
||||
- [1, 5, 3]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "min"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 5, 3] into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call min(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 1
|
||||
|
||||
# Sum Function Tests
|
||||
- note: builtin_sum_array
|
||||
description: Test sum builtin with numeric array
|
||||
example_rego: "sum([1, 2, 3, 4])"
|
||||
literals:
|
||||
- [1, 2, 3, 4]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "sum"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load [1, 2, 3, 4] into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call sum(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 10
|
||||
|
||||
- note: builtin_sum_empty_array
|
||||
description: Test sum builtin with empty array
|
||||
example_rego: "sum([])"
|
||||
literals:
|
||||
- []
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "sum"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load empty array into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call sum(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 0
|
||||
|
||||
# String Functions Tests
|
||||
- note: builtin_upper_string
|
||||
description: Test upper builtin function
|
||||
example_rego: "upper(\"hello\")"
|
||||
literals:
|
||||
- "hello"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "upper"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call upper(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "HELLO"
|
||||
|
||||
- note: builtin_lower_string
|
||||
description: Test lower builtin function
|
||||
example_rego: "lower(\"WORLD\")"
|
||||
literals:
|
||||
- "WORLD"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "lower"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "WORLD" into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call lower(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "world"
|
||||
|
||||
# Multi-argument Builtin Tests
|
||||
- note: builtin_contains_string
|
||||
description: Test contains builtin with string arguments
|
||||
example_rego: "contains(\"hello world\", \"world\")"
|
||||
literals:
|
||||
- "hello world"
|
||||
- "world"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "contains"
|
||||
num_args: 2
|
||||
builtin_call_params:
|
||||
- dest: 2
|
||||
builtin_index: 0
|
||||
args: [0, 1]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "hello world" into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load "world" into register 1
|
||||
- "BuiltinCall { params_index: 0 }" # Call contains(register[0], register[1]), result in register 2
|
||||
- "Return { value: 2 }" # Return result
|
||||
want_result: true
|
||||
|
||||
- note: builtin_contains_string_false
|
||||
description: Test contains builtin with non-matching strings
|
||||
example_rego: "contains(\"hello\", \"world\")"
|
||||
literals:
|
||||
- "hello"
|
||||
- "world"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "contains"
|
||||
num_args: 2
|
||||
builtin_call_params:
|
||||
- dest: 2
|
||||
builtin_index: 0
|
||||
args: [0, 1]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load "world" into register 1
|
||||
- "BuiltinCall { params_index: 0 }" # Call contains(register[0], register[1]), result in register 2
|
||||
- "Return { value: 2 }" # Return result
|
||||
want_result: false
|
||||
|
||||
# Array Function Tests
|
||||
- note: builtin_sort_array
|
||||
description: Test sort builtin with array
|
||||
example_rego: "sort([3, 1, 4, 1, 5])"
|
||||
literals:
|
||||
- [3, 1, 4, 1, 5]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "sort"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load unsorted array into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call sort(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: [1, 1, 3, 4, 5]
|
||||
|
||||
# Type Function Tests
|
||||
- note: builtin_type_string
|
||||
description: Test type_name builtin with string
|
||||
example_rego: "type_name(\"hello\")"
|
||||
literals:
|
||||
- "hello"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "type_name"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "hello" into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "string"
|
||||
|
||||
- note: builtin_type_number
|
||||
description: Test type_name builtin with number
|
||||
example_rego: "type_name(42)"
|
||||
literals:
|
||||
- 42
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "type_name"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "number"
|
||||
|
||||
- note: builtin_type_array
|
||||
description: Test type_name builtin with array
|
||||
example_rego: "type_name([1, 2, 3])"
|
||||
literals:
|
||||
- [1, 2, 3]
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "type_name"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load array into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call type_name(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: "array"
|
||||
|
||||
# Complex Builtin Chain Tests
|
||||
- note: builtin_chained_operations
|
||||
description: Test chaining multiple builtin calls
|
||||
example_rego: "upper(lower(\"HELLO\"))"
|
||||
literals:
|
||||
- "HELLO"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "lower"
|
||||
num_args: 1
|
||||
- name: "upper"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
- dest: 2
|
||||
builtin_index: 1
|
||||
args: [1]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load "HELLO" into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call lower(register[0]), result in register 1
|
||||
- "BuiltinCall { params_index: 1 }" # Call upper(register[1]), result in register 2
|
||||
- "Return { value: 2 }" # Return final result
|
||||
want_result: "HELLO"
|
||||
|
||||
# Number Function Tests
|
||||
- note: builtin_abs_positive
|
||||
description: Test abs builtin with positive number
|
||||
example_rego: "abs(42)"
|
||||
literals:
|
||||
- 42
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "abs"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call abs(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 42
|
||||
|
||||
- note: builtin_abs_negative
|
||||
description: Test abs builtin with negative number
|
||||
example_rego: "abs(-42)"
|
||||
literals:
|
||||
- -42
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "abs"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load -42 into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call abs(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: 42
|
||||
|
||||
# Set Function Tests - Commented out due to set literal parsing issues
|
||||
# TODO: Add proper set tests once set literal parsing is fixed
|
||||
# - note: builtin_union_sets
|
||||
# description: Test union builtin with sets
|
||||
# ...
|
||||
|
||||
# Error Handling Tests
|
||||
- note: builtin_missing_function
|
||||
description: Test calling non-existent builtin function
|
||||
example_rego: "nonexistent_function(42)"
|
||||
literals:
|
||||
- 42
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "nonexistent_function"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call nonexistent_function(register[0]), should error
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_error: "Missing builtin function: nonexistent_function"
|
||||
|
||||
# Test sets and union builtin
|
||||
- note: "union([{1, 2}, {2, 3}]) should return {1, 2, 3}"
|
||||
description: Test union builtin with set of sets
|
||||
example_rego: "union([{1, 2}, {2, 3}])"
|
||||
literals:
|
||||
-
|
||||
set!:
|
||||
-
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
-
|
||||
set!:
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "BuiltinCall { params_index: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "union"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
# Boolean Functions
|
||||
- note: builtin_is_boolean_true
|
||||
description: Test is_boolean builtin with true value
|
||||
example_rego: "is_boolean(true)"
|
||||
literals:
|
||||
- true
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "is_boolean"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load true into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: true
|
||||
|
||||
- note: builtin_is_boolean_false
|
||||
description: Test is_boolean builtin with false value
|
||||
example_rego: "is_boolean(false)"
|
||||
literals:
|
||||
- false
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "is_boolean"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load false into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: true
|
||||
|
||||
- note: builtin_is_boolean_number
|
||||
description: Test is_boolean builtin with number value (should return false)
|
||||
example_rego: "is_boolean(42)"
|
||||
literals:
|
||||
- 42
|
||||
instruction_params:
|
||||
builtin_infos:
|
||||
- name: "is_boolean"
|
||||
num_args: 1
|
||||
builtin_call_params:
|
||||
- dest: 1
|
||||
builtin_index: 0
|
||||
args: [0]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 42 into register 0
|
||||
- "BuiltinCall { params_index: 0 }" # Call is_boolean(register[0]), result in register 1
|
||||
- "Return { value: 1 }" # Return result
|
||||
want_result: false
|
||||
76
tests/rvm/vm/suites/call_rule.yaml
Normal file
76
tests/rvm/vm/suites/call_rule.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
# CallRule Test Suite
|
||||
# Validates rule caching, partial structures, defaults, and inconsistencies.
|
||||
|
||||
cases:
|
||||
- note: call_rule_basic_cache
|
||||
description: CallRule caches results after first evaluation
|
||||
literals:
|
||||
- {}
|
||||
- 1
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [3]
|
||||
rule_tree:
|
||||
data:
|
||||
test:
|
||||
allow: 0
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "CallRule { dest: 2, rule_index: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: 1
|
||||
|
||||
- note: call_rule_partial_object_default
|
||||
description: Partial object rule returns object even when no fields defined
|
||||
literals:
|
||||
- {}
|
||||
rule_infos:
|
||||
- rule_type: PartialObject
|
||||
definitions:
|
||||
- [2]
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: {}
|
||||
|
||||
- note: call_rule_default_literal
|
||||
description: Default literal used when complete rule returns undefined
|
||||
literals:
|
||||
- "default"
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [2]
|
||||
default_literal_index: 0
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: "default"
|
||||
|
||||
- note: call_rule_inconsistency
|
||||
description: Complete rule with inconsistent results returns undefined
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [2, 5]
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "RuleReturn {}"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: "#undefined"
|
||||
111
tests/rvm/vm/suites/comparison_operations.yaml
Normal file
111
tests/rvm/vm/suites/comparison_operations.yaml
Normal file
@@ -0,0 +1,111 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Comparison Operations Test Suite
|
||||
# Tests comparison instructions: Eq, Ne, Lt, Le, Gt, Ge
|
||||
# These instructions compare values and produce boolean results
|
||||
|
||||
cases:
|
||||
- note: comparison_eq
|
||||
description: Test Eq instruction
|
||||
example_rego: "5 == 5" # Equality comparison
|
||||
literals:
|
||||
- 5
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
|
||||
- "Eq { dest: 2, left: 0, right: 1 }" # Compare register 0 == register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_eq_false
|
||||
description: Test Eq instruction with false result
|
||||
example_rego: "5 == 3" # Equality comparison (false case)
|
||||
literals:
|
||||
- 5
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
|
||||
- "Eq { dest: 2, left: 0, right: 1 }" # Compare register 0 == register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: false
|
||||
|
||||
- note: comparison_ne
|
||||
description: Test Ne instruction
|
||||
example_rego: "5 != 3" # Inequality comparison
|
||||
literals:
|
||||
- 5
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
|
||||
- "Ne { dest: 2, left: 0, right: 1 }" # Compare register 0 != register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_lt
|
||||
description: Test Lt instruction
|
||||
example_rego: "3 < 5" # Less than comparison
|
||||
literals:
|
||||
- 3
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 3 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
|
||||
- "Lt { dest: 2, left: 0, right: 1 }" # Compare register 0 < register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_le
|
||||
description: Test Le instruction (less than or equal)
|
||||
example_rego: "5 <= 10" # Less than or equal comparison
|
||||
literals:
|
||||
- 5
|
||||
- 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 10 into register 1
|
||||
- "Le { dest: 2, left: 0, right: 1 }" # Compare register 0 <= register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_ge
|
||||
description: Test Ge instruction (greater than or equal)
|
||||
example_rego: "10 >= 5" # Greater than or equal comparison
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
|
||||
- "Ge { dest: 2, left: 0, right: 1 }" # Compare register 0 >= register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_gt
|
||||
description: Test Gt instruction
|
||||
example_rego: "7 > 3" # Greater than comparison
|
||||
literals:
|
||||
- 7
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 7 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 3 into register 1
|
||||
- "Gt { dest: 2, left: 0, right: 1 }" # Compare register 0 > register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
|
||||
- note: comparison_ge_equal
|
||||
description: Test Ge instruction with equal values
|
||||
example_rego: "5 >= 5" # Greater than or equal comparison (equal case)
|
||||
literals:
|
||||
- 5
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load literal 5 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load literal 5 into register 1
|
||||
- "Ge { dest: 2, left: 0, right: 1 }" # Compare register 0 >= register 1, store result in register 2
|
||||
- "Return { value: 2 }" # Return boolean result from register 2
|
||||
want_result: true
|
||||
230
tests/rvm/vm/suites/complex.yaml
Normal file
230
tests/rvm/vm/suites/complex.yaml
Normal file
@@ -0,0 +1,230 @@
|
||||
# Complex VM Control Flow Suite
|
||||
# Exercises deep nesting of heterogeneous loops, comprehensions, and rule calls.
|
||||
|
||||
cases:
|
||||
- note: twenty_level_loop_maze
|
||||
description: Twenty levels of mixed loop modes with embedded comprehension and rule call
|
||||
literals:
|
||||
- 0
|
||||
- 1
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 10
|
||||
body_start: 5
|
||||
loop_end: 55
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 11
|
||||
body_start: 6
|
||||
loop_end: 54
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 12
|
||||
body_start: 7
|
||||
loop_end: 53
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 13
|
||||
body_start: 8
|
||||
loop_end: 52
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 14
|
||||
body_start: 9
|
||||
loop_end: 51
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 15
|
||||
body_start: 10
|
||||
loop_end: 50
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 16
|
||||
body_start: 11
|
||||
loop_end: 49
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 17
|
||||
body_start: 12
|
||||
loop_end: 48
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 18
|
||||
body_start: 13
|
||||
loop_end: 47
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 19
|
||||
body_start: 14
|
||||
loop_end: 46
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 20
|
||||
body_start: 15
|
||||
loop_end: 45
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 21
|
||||
body_start: 16
|
||||
loop_end: 44
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 22
|
||||
body_start: 17
|
||||
loop_end: 43
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 23
|
||||
body_start: 18
|
||||
loop_end: 42
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 24
|
||||
body_start: 19
|
||||
loop_end: 41
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 25
|
||||
body_start: 20
|
||||
loop_end: 40
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 26
|
||||
body_start: 21
|
||||
loop_end: 39
|
||||
- mode: "Every"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 27
|
||||
body_start: 22
|
||||
loop_end: 38
|
||||
- mode: "ForEach"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 28
|
||||
body_start: 23
|
||||
loop_end: 37
|
||||
- mode: "Any"
|
||||
collection: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 29
|
||||
body_start: 24
|
||||
loop_end: 36
|
||||
- mode: "ForEach"
|
||||
collection: 4
|
||||
key_reg: 30
|
||||
value_reg: 31
|
||||
result_reg: 32
|
||||
body_start: 28
|
||||
loop_end: 31
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 4
|
||||
key_reg: 30
|
||||
value_reg: 31
|
||||
body_start: 28
|
||||
comprehension_end: 31
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [56]
|
||||
instructions:
|
||||
- "ArrayNew { dest: 1 }"
|
||||
- "Load { dest: 8, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 1, value: 8 }"
|
||||
- "Load { dest: 7, literal_idx: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
- "LoopStart { params_index: 2 }"
|
||||
- "LoopStart { params_index: 3 }"
|
||||
- "LoopStart { params_index: 4 }"
|
||||
- "LoopStart { params_index: 5 }"
|
||||
- "LoopStart { params_index: 6 }"
|
||||
- "LoopStart { params_index: 7 }"
|
||||
- "LoopStart { params_index: 8 }"
|
||||
- "LoopStart { params_index: 9 }"
|
||||
- "LoopStart { params_index: 10 }"
|
||||
- "LoopStart { params_index: 11 }"
|
||||
- "LoopStart { params_index: 12 }"
|
||||
- "LoopStart { params_index: 13 }"
|
||||
- "LoopStart { params_index: 14 }"
|
||||
- "LoopStart { params_index: 15 }"
|
||||
- "LoopStart { params_index: 16 }"
|
||||
- "LoopStart { params_index: 17 }"
|
||||
- "LoopStart { params_index: 18 }"
|
||||
- "LoopStart { params_index: 19 }"
|
||||
- "ArrayNew { dest: 4 }"
|
||||
- "ArrayPush { arr: 4, value: 8 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 20 }"
|
||||
- "Move { dest: 31, src: 8 }"
|
||||
- "ComprehensionYield { value_reg: 31 }"
|
||||
- "LoopNext { body_start: 28, loop_end: 31 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Add { dest: 7, left: 7, right: 8 }"
|
||||
- "CallRule { dest: 9, rule_index: 0 }"
|
||||
- "Add { dest: 7, left: 7, right: 9 }"
|
||||
- "LoopNext { body_start: 24, loop_end: 36 }"
|
||||
- "LoopNext { body_start: 23, loop_end: 37 }"
|
||||
- "LoopNext { body_start: 22, loop_end: 38 }"
|
||||
- "LoopNext { body_start: 21, loop_end: 39 }"
|
||||
- "LoopNext { body_start: 20, loop_end: 40 }"
|
||||
- "LoopNext { body_start: 19, loop_end: 41 }"
|
||||
- "LoopNext { body_start: 18, loop_end: 42 }"
|
||||
- "LoopNext { body_start: 17, loop_end: 43 }"
|
||||
- "LoopNext { body_start: 16, loop_end: 44 }"
|
||||
- "LoopNext { body_start: 15, loop_end: 45 }"
|
||||
- "LoopNext { body_start: 14, loop_end: 46 }"
|
||||
- "LoopNext { body_start: 13, loop_end: 47 }"
|
||||
- "LoopNext { body_start: 12, loop_end: 48 }"
|
||||
- "LoopNext { body_start: 11, loop_end: 49 }"
|
||||
- "LoopNext { body_start: 10, loop_end: 50 }"
|
||||
- "LoopNext { body_start: 9, loop_end: 51 }"
|
||||
- "LoopNext { body_start: 8, loop_end: 52 }"
|
||||
- "LoopNext { body_start: 7, loop_end: 53 }"
|
||||
- "LoopNext { body_start: 6, loop_end: 54 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 55 }"
|
||||
- "Return { value: 7 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: 2
|
||||
80
tests/rvm/vm/suites/constructed_collections.yaml
Normal file
80
tests/rvm/vm/suites/constructed_collections.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Constructed Collections Test Suite
|
||||
# Validates ArrayCreate and SetCreate behavior including undefined handling.
|
||||
|
||||
cases:
|
||||
- note: array_create_basic
|
||||
description: ArrayCreate assembles elements from registers
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 3
|
||||
elements: [0, 1, 2]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- note: array_create_with_undefined
|
||||
description: ArrayCreate returns undefined if any source is undefined
|
||||
literals:
|
||||
- 1
|
||||
- {}
|
||||
- "missing"
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 2
|
||||
elements: [0, 1]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "Index { dest: 1, container: 3, key: 4 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: set_create_basic
|
||||
description: SetCreate deduplicates values and collects registers
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
set_create_params:
|
||||
- dest: 3
|
||||
elements: [0, 1, 2]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "SetCreate { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result:
|
||||
set!: [1, 2]
|
||||
|
||||
- note: set_create_with_undefined
|
||||
description: SetCreate returns undefined if any source register is undefined
|
||||
literals:
|
||||
- {}
|
||||
- "missing"
|
||||
instruction_params:
|
||||
set_create_params:
|
||||
- dest: 3
|
||||
elements: [0]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 0, container: 1, key: 2 }"
|
||||
- "SetCreate { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: "#undefined"
|
||||
115
tests/rvm/vm/suites/control_flow.yaml
Normal file
115
tests/rvm/vm/suites/control_flow.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Control Flow Test Suite
|
||||
# Tests conditional execution and branching patterns
|
||||
# Focuses on AssertCondition and conditional logic without loops
|
||||
|
||||
cases:
|
||||
- note: assert_condition_true
|
||||
description: Test AssertCondition with true condition
|
||||
example_rego: "x > 5; x := 10" # Simple condition that should succeed
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
|
||||
- "Gt { dest: 2, left: 0, right: 1 }" # Check if 10 > 5, store result in register 2
|
||||
- "AssertCondition { condition: 2 }" # Assert the condition (should succeed)
|
||||
- "Return { value: 0 }" # Return the original value
|
||||
want_result: 10
|
||||
|
||||
- note: assert_condition_false
|
||||
description: Test AssertCondition with false condition
|
||||
example_rego: "x > 15; x := 10" # Simple condition that should fail
|
||||
literals:
|
||||
- 10
|
||||
- 15
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 15 into register 1
|
||||
- "Gt { dest: 2, left: 0, right: 1 }" # Check if 10 > 15, store result in register 2
|
||||
- "AssertCondition { condition: 2 }" # Assert the condition (should fail)
|
||||
- "Return { value: 0 }" # Return the original value (never reached)
|
||||
want_result: "#undefined" # VM should return undefined for failed assertion
|
||||
|
||||
- note: complex_condition_and
|
||||
description: Test complex AND condition
|
||||
example_rego: "x > 5; x < 15; x := 10" # Multiple conditions (both must be true)
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
- 15
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load 15 into register 2
|
||||
- "Gt { dest: 3, left: 0, right: 1 }" # Check if 10 > 5, store result in register 3
|
||||
- "Lt { dest: 4, left: 0, right: 2 }" # Check if 10 < 15, store result in register 4
|
||||
- "And { dest: 5, left: 3, right: 4 }" # AND both conditions, store result in register 5
|
||||
- "AssertCondition { condition: 5 }" # Assert the combined condition (should succeed)
|
||||
- "Return { value: 0 }" # Return the original value
|
||||
want_result: 10
|
||||
|
||||
- note: complex_condition_or
|
||||
description: Test complex OR condition
|
||||
example_rego: "x < 5; x > 15; x := 10" # Either condition can be true (both are false here)
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
- 15
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 10 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 5 into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load 15 into register 2
|
||||
- "Lt { dest: 3, left: 0, right: 1 }" # Check if 10 < 5, store result in register 3
|
||||
- "Gt { dest: 4, left: 0, right: 2 }" # Check if 10 > 15, store result in register 4
|
||||
- "Or { dest: 5, left: 3, right: 4 }" # OR both conditions, store result in register 5
|
||||
- "AssertCondition { condition: 5 }" # Assert the combined condition (should fail)
|
||||
- "Return { value: 0 }" # Return the original value (never reached)
|
||||
want_result: "#undefined" # VM should return undefined for failed assertion
|
||||
|
||||
- note: conditional_value_selection
|
||||
description: Test conditional value selection
|
||||
example_rego: "result := x > 10 ? x * 2 : x * 3; x := 15" # Conditional expression simulation
|
||||
literals:
|
||||
- 15
|
||||
- 10
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load 15 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load 10 into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load 2 into register 2
|
||||
- "Load { dest: 3, literal_idx: 3 }" # Load 3 into register 3
|
||||
- "Gt { dest: 4, left: 0, right: 1 }" # Check if 15 > 10, store result in register 4
|
||||
- "Mul { dest: 5, left: 0, right: 2 }" # Compute 15 * 2, store in register 5
|
||||
- "Mul { dest: 6, left: 0, right: 3 }" # Compute 15 * 3, store in register 6
|
||||
# Simulate conditional selection (in real VM this would use conditional instructions)
|
||||
- "AssertCondition { condition: 4 }" # Since condition is true, we proceed with first value
|
||||
- "Return { value: 5 }" # Return x * 2 (30)
|
||||
want_result: 30
|
||||
|
||||
- note: nested_conditions
|
||||
description: Test nested conditional logic
|
||||
example_rego: "x > 0; y > 0; z := x + y; z > 10; x := 8; y := 5" # Nested conditions with intermediate calculation
|
||||
literals:
|
||||
- 8
|
||||
- 5
|
||||
- 0
|
||||
- 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }" # Load x=8 into register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load y=5 into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load 0 into register 2
|
||||
- "Load { dest: 3, literal_idx: 3 }" # Load 10 into register 3
|
||||
- "Gt { dest: 4, left: 0, right: 2 }" # Check if x > 0, store result in register 4
|
||||
- "AssertCondition { condition: 4 }" # Assert x > 0 (should succeed)
|
||||
- "Gt { dest: 5, left: 1, right: 2 }" # Check if y > 0, store result in register 5
|
||||
- "AssertCondition { condition: 5 }" # Assert y > 0 (should succeed)
|
||||
- "Add { dest: 6, left: 0, right: 1 }" # Compute z = x + y, store in register 6
|
||||
- "Gt { dest: 7, left: 6, right: 3 }" # Check if z > 10, store result in register 7
|
||||
- "AssertCondition { condition: 7 }" # Assert z > 10 (should succeed: 13 > 10)
|
||||
- "Return { value: 6 }" # Return z value (13)
|
||||
want_result: 13
|
||||
232
tests/rvm/vm/suites/core_semantics.yaml
Normal file
232
tests/rvm/vm/suites/core_semantics.yaml
Normal file
@@ -0,0 +1,232 @@
|
||||
# Comprehensive VM semantics regression suite
|
||||
# Covers arithmetic, comparisons, loop modes, assertions, and collection helpers.
|
||||
|
||||
cases:
|
||||
- note: arithmetic_ops
|
||||
description: Verify primitive arithmetic instructions and array aggregation
|
||||
literals:
|
||||
- 8
|
||||
- 2
|
||||
instructions:
|
||||
- "ArrayNew { dest: 5 }"
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 2 }"
|
||||
- "Sub { dest: 3, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 3 }"
|
||||
- "Mul { dest: 4, left: 3, right: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 4 }"
|
||||
- "Div { dest: 6, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 6 }"
|
||||
- "Mod { dest: 7, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 7 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: [10, 6, 12, 4, 0]
|
||||
|
||||
- note: comparison_and_logic
|
||||
description: Validate comparison, boolean, and negation instructions
|
||||
literals:
|
||||
- 5
|
||||
- 5
|
||||
- 3
|
||||
instructions:
|
||||
- "ArrayNew { dest: 11 }"
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 2 }"
|
||||
- "Eq { dest: 3, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 11, value: 3 }"
|
||||
- "Ne { dest: 4, left: 0, right: 1 }"
|
||||
- "ArrayPush { arr: 11, value: 4 }"
|
||||
- "Lt { dest: 5, left: 2, right: 0 }"
|
||||
- "ArrayPush { arr: 11, value: 5 }"
|
||||
- "Ge { dest: 6, left: 0, right: 2 }"
|
||||
- "ArrayPush { arr: 11, value: 6 }"
|
||||
- "And { dest: 7, left: 3, right: 5 }"
|
||||
- "ArrayPush { arr: 11, value: 7 }"
|
||||
- "Not { dest: 8, operand: 4 }"
|
||||
- "ArrayPush { arr: 11, value: 8 }"
|
||||
- "Or { dest: 9, left: 4, right: 5 }"
|
||||
- "ArrayPush { arr: 11, value: 9 }"
|
||||
- "Return { value: 11 }"
|
||||
want_result: [true, false, true, true, true, true, true]
|
||||
|
||||
- note: loop_any_short_circuit_success
|
||||
description: LoopMode::Any should exit on first successful iteration
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 6
|
||||
value_reg: 7
|
||||
result_reg: 8
|
||||
body_start: 9
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Eq { dest: 5, left: 7, right: 4 }"
|
||||
- "AssertCondition { condition: 5 }"
|
||||
- "LoopNext { body_start: 9, loop_end: 11 }"
|
||||
- "Return { value: 8 }"
|
||||
want_result: true
|
||||
|
||||
- note: loop_any_all_fail
|
||||
description: LoopMode::Any should remain false when no iteration succeeds
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 99
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 6
|
||||
value_reg: 7
|
||||
result_reg: 8
|
||||
body_start: 9
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Eq { dest: 5, left: 7, right: 4 }"
|
||||
- "AssertCondition { condition: 5 }"
|
||||
- "LoopNext { body_start: 9, loop_end: 11 }"
|
||||
- "Return { value: 8 }"
|
||||
want_result: false
|
||||
|
||||
- note: loop_every_exits_on_failure
|
||||
description: LoopMode::Every stops at the first failed iteration and returns false
|
||||
literals:
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 6
|
||||
value_reg: 7
|
||||
result_reg: 8
|
||||
body_start: 10
|
||||
loop_end: 14
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "ArrayNew { dest: 9 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "ArrayPush { arr: 9, value: 7 }"
|
||||
- "Eq { dest: 5, left: 7, right: 4 }"
|
||||
- "AssertCondition { condition: 5 }"
|
||||
- "LoopNext { body_start: 10, loop_end: 13 }"
|
||||
- "ArrayNew { dest: 10 }"
|
||||
- "ArrayPush { arr: 10, value: 8 }"
|
||||
- "ArrayPush { arr: 10, value: 9 }"
|
||||
- "Return { value: 10 }"
|
||||
want_result:
|
||||
- false
|
||||
- [1, 0]
|
||||
|
||||
- note: loop_foreach_collects_successes
|
||||
description: LoopMode::ForEach tracks successful iterations while visiting all elements
|
||||
literals:
|
||||
- 0
|
||||
- 5
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 6
|
||||
value_reg: 7
|
||||
result_reg: 8
|
||||
body_start: 9
|
||||
loop_end: 13
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "ArrayNew { dest: 9 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Eq { dest: 4, left: 7, right: 3 }"
|
||||
- "ArrayPush { arr: 9, value: 4 }"
|
||||
- "AssertCondition { condition: 4 }"
|
||||
- "LoopNext { body_start: 9, loop_end: 12 }"
|
||||
- "ArrayNew { dest: 10 }"
|
||||
- "ArrayPush { arr: 10, value: 8 }"
|
||||
- "ArrayPush { arr: 10, value: 9 }"
|
||||
- "Return { value: 10 }"
|
||||
want_result:
|
||||
- true
|
||||
- [false, false, true]
|
||||
|
||||
- note: assert_condition_without_loop_errors
|
||||
description: AssertCondition outside of a loop reports an assertion failure
|
||||
literals:
|
||||
- false
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "AssertCondition { condition: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_error: "Assertion failed"
|
||||
|
||||
- note: contains_and_count_helpers
|
||||
description: Contains and Count semantics across arrays, sets, and non-collections
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 9
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "SetNew { dest: 3 }"
|
||||
- "SetAdd { set: 3, value: 1 }"
|
||||
- "SetAdd { set: 3, value: 2 }"
|
||||
- "Contains { dest: 4, collection: 0, value: 1 }"
|
||||
- "Contains { dest: 5, collection: 3, value: 2 }"
|
||||
- "Load { dest: 6, literal_idx: 2 }"
|
||||
- "Contains { dest: 7, collection: 3, value: 6 }"
|
||||
- "Count { dest: 8, collection: 0 }"
|
||||
- "Count { dest: 9, collection: 3 }"
|
||||
- "Count { dest: 10, collection: 1 }"
|
||||
- "ArrayNew { dest: 11 }"
|
||||
- "ArrayPush { arr: 11, value: 4 }"
|
||||
- "ArrayPush { arr: 11, value: 5 }"
|
||||
- "ArrayPush { arr: 11, value: 7 }"
|
||||
- "ArrayPush { arr: 11, value: 8 }"
|
||||
- "ArrayPush { arr: 11, value: 9 }"
|
||||
- "ArrayPush { arr: 11, value: 10 }"
|
||||
- "Return { value: 11 }"
|
||||
want_result: [true, true, false, 2, 2, "#undefined"]
|
||||
76
tests/rvm/vm/suites/data_structures.yaml
Normal file
76
tests/rvm/vm/suites/data_structures.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Data Structures Test Suite
|
||||
# Tests instructions for working with arrays, objects, and sets
|
||||
# These instructions create and manipulate complex data types
|
||||
|
||||
cases:
|
||||
- note: array_operations
|
||||
description: Test array creation and access
|
||||
example_rego: "arr := [1, 2, 3]; arr[0]" # Array creation and indexing
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 0 # index
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty array in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "Load { dest: 4, literal_idx: 3 }" # Load index 0 into register 4
|
||||
- "Index { dest: 5, container: 0, key: 4 }" # Access array[0], store in register 5
|
||||
- "Return { value: 5 }" # Return the indexed value
|
||||
want_result: 1
|
||||
|
||||
- note: object_operations
|
||||
description: Test object creation and access
|
||||
example_rego: "obj := {\"key1\": \"value1\", \"key2\": 42}; obj.key1" # Object creation and field access
|
||||
literals:
|
||||
- {}
|
||||
- "key1"
|
||||
- "value1"
|
||||
- "key2"
|
||||
- 42
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }" # Create empty object in register 0
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load "key1" into register 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load "value1" into register 2
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }" # Set obj["key1"] = "value1"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # Load "key2" into register 3
|
||||
- "Load { dest: 4, literal_idx: 4 }" # Load 42 into register 4
|
||||
- "ObjectSet { obj: 0, key: 3, value: 4 }" # Set obj["key2"] = 42
|
||||
- "Load { dest: 5, literal_idx: 1 }" # Load "key1" again for lookup
|
||||
- "Index { dest: 6, container: 0, key: 5 }" # Access obj["key1"], store in register 6
|
||||
- "Return { value: 6 }" # Return the field value
|
||||
want_result: "value1"
|
||||
|
||||
- note: set_operations
|
||||
description: Test set creation and membership
|
||||
example_rego: "s := {1, 2, 3}; 2 in s" # Set creation and membership test
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }" # Create empty set in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "SetAdd { set: 0, value: 1 }" # Add 1 to set
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "SetAdd { set: 0, value: 2 }" # Add 2 to set
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "SetAdd { set: 0, value: 3 }" # Add 3 to set
|
||||
- "Load { dest: 4, literal_idx: 1 }" # Load 2 again for membership check
|
||||
- "Contains { dest: 5, collection: 0, value: 4 }" # Check if 2 is in set, store result in register 5
|
||||
- "Return { value: 5 }" # Return boolean membership result
|
||||
want_result: true
|
||||
278
tests/rvm/vm/suites/deep_nesting.yaml
Normal file
278
tests/rvm/vm/suites/deep_nesting.yaml
Normal file
@@ -0,0 +1,278 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Deep Nesting Test Suite
|
||||
# Tests 3+ levels of nested loops with various loop modes and comprehensions
|
||||
# Verifies register allocation, stack pressure, and control flow correctness
|
||||
|
||||
cases:
|
||||
- note: three_level_nested_any_every_foreach
|
||||
description: 3-level nesting - Any inside Every inside ForEach
|
||||
example_rego: |
|
||||
[outer |
|
||||
outer := [1, 2][_];
|
||||
every mid in [1, 2]; some inner in [1, 2]; inner == mid
|
||||
]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 0
|
||||
result_reg: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 7
|
||||
comprehension_end: 27
|
||||
loop_params:
|
||||
- mode: "ForEach" # Outer loop for comprehension
|
||||
collection: 5
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 7
|
||||
loop_end: 27
|
||||
- mode: "Every" # Middle loop
|
||||
collection: 8
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 13
|
||||
loop_end: 24
|
||||
- mode: "Any" # Inner loop
|
||||
collection: 13
|
||||
key_reg: 30
|
||||
value_reg: 31
|
||||
result_reg: 32
|
||||
body_start: 19
|
||||
loop_end: 22
|
||||
instructions:
|
||||
# Build outer collection [1, 2]
|
||||
- "ArrayNew { dest: 5 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 5, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 2 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }" # ForEach outer
|
||||
# Build middle collection [1, 2]
|
||||
- "ArrayNew { dest: 8 }"
|
||||
- "Load { dest: 3, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 8, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 8, value: 4 }"
|
||||
- "LoopStart { params_index: 1 }" # Every middle
|
||||
# Build inner collection [1, 2]
|
||||
- "ArrayNew { dest: 13 }"
|
||||
- "Load { dest: 6, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 13, value: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 13, value: 7 }"
|
||||
- "LoopStart { params_index: 2 }" # Any inner
|
||||
# Check condition: inner == mid
|
||||
- "Eq { dest: 40, left: 31, right: 21 }"
|
||||
- "AssertCondition { condition: 40 }"
|
||||
- "LoopNext { body_start: 19, loop_end: 22 }"
|
||||
# End Any loop - check result
|
||||
- "AssertCondition { condition: 32 }"
|
||||
- "LoopNext { body_start: 13, loop_end: 24 }"
|
||||
# End Every loop - check result
|
||||
- "AssertCondition { condition: 22 }"
|
||||
- "ComprehensionYield { value_reg: 11 }"
|
||||
- "LoopNext { body_start: 7, loop_end: 27 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [1, 2]
|
||||
|
||||
- note: four_level_nested_loops
|
||||
description: 4-level nested loops - stress test for stack depth
|
||||
example_rego: |
|
||||
some a in [1]; some b in [2]; some c in [3]; some d in [4]; a + b + c + d == 10
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 10
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 4
|
||||
loop_end: 29
|
||||
- mode: "Any"
|
||||
collection: 2
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 8
|
||||
loop_end: 27
|
||||
- mode: "Any"
|
||||
collection: 4
|
||||
key_reg: 30
|
||||
value_reg: 31
|
||||
result_reg: 32
|
||||
body_start: 12
|
||||
loop_end: 25
|
||||
- mode: "Any"
|
||||
collection: 6
|
||||
key_reg: 40
|
||||
value_reg: 41
|
||||
result_reg: 42
|
||||
body_start: 16
|
||||
loop_end: 23
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "LoopStart { params_index: 0 }" # Level 1
|
||||
- "ArrayNew { dest: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 2, value: 3 }"
|
||||
- "LoopStart { params_index: 1 }" # Level 2
|
||||
- "ArrayNew { dest: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 4, value: 5 }"
|
||||
- "LoopStart { params_index: 2 }" # Level 3
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 6, value: 7 }"
|
||||
- "LoopStart { params_index: 3 }" # Level 4
|
||||
# Compute sum: a + b + c + d
|
||||
- "Add { dest: 43, left: 11, right: 21 }"
|
||||
- "Add { dest: 44, left: 43, right: 31 }"
|
||||
- "Add { dest: 45, left: 44, right: 41 }"
|
||||
- "Load { dest: 46, literal_idx: 4 }"
|
||||
- "Eq { dest: 47, left: 45, right: 46 }"
|
||||
- "AssertCondition { condition: 47 }"
|
||||
- "LoopNext { body_start: 16, loop_end: 23 }"
|
||||
- "AssertCondition { condition: 42 }"
|
||||
- "LoopNext { body_start: 12, loop_end: 25 }"
|
||||
- "AssertCondition { condition: 32 }"
|
||||
- "LoopNext { body_start: 8, loop_end: 27 }"
|
||||
- "AssertCondition { condition: 22 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 29 }"
|
||||
- "Return { value: 12 }"
|
||||
want_result: true
|
||||
|
||||
- note: comprehension_inside_nested_loop
|
||||
description: Array comprehension inside 2-level nested loop
|
||||
example_rego: |
|
||||
[[y | y := inner[_]] |
|
||||
outer := [[1, 2], [3, 4]][_];
|
||||
inner := outer
|
||||
]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array" # Outer comprehension
|
||||
collection_reg: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 15
|
||||
comprehension_end: 23
|
||||
- mode: "Array" # Inner comprehension
|
||||
collection_reg: 14
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
body_start: 18
|
||||
comprehension_end: 20
|
||||
loop_params:
|
||||
- mode: "ForEach" # Outer loop
|
||||
collection: 8
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 13
|
||||
body_start: 15
|
||||
loop_end: 23
|
||||
- mode: "ForEach" # Inner loop for comprehension
|
||||
collection: 12
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 18
|
||||
loop_end: 20
|
||||
instructions:
|
||||
# Build outer array [[1, 2], [3, 4]]
|
||||
- "ArrayNew { dest: 8 }"
|
||||
- "ArrayNew { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 1, value: 3 }"
|
||||
- "ArrayPush { arr: 8, value: 1 }"
|
||||
- "ArrayNew { dest: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 4, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 4, value: 6 }"
|
||||
- "ArrayPush { arr: 8, value: 4 }"
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start outer comprehension
|
||||
- "LoopStart { params_index: 0 }" # ForEach over outer array
|
||||
- "Move { dest: 12, src: 11 }" # inner := outer
|
||||
- "ComprehensionBegin { params_index: 1 }" # Start inner comprehension
|
||||
- "LoopStart { params_index: 1 }" # ForEach over inner array
|
||||
- "ComprehensionYield { value_reg: 21 }" # Yield y
|
||||
- "LoopNext { body_start: 18, loop_end: 20 }"
|
||||
- "ComprehensionEnd" # End inner comprehension
|
||||
- "ComprehensionYield { value_reg: 14 }" # Yield inner comprehension result
|
||||
- "LoopNext { body_start: 15, loop_end: 23 }"
|
||||
- "ComprehensionEnd" # End outer comprehension
|
||||
- "Return { value: 0 }"
|
||||
want_result: [[1, 2], [3, 4]]
|
||||
|
||||
- note: register_pressure_nested_loops
|
||||
description: Nested loops with 15+ active registers to test allocation
|
||||
example_rego: |
|
||||
some a in [1]; some b in [2]; a + b > 0
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 0
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 4
|
||||
loop_end: 19
|
||||
- mode: "Any"
|
||||
collection: 2
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 8
|
||||
loop_end: 17
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "ArrayNew { dest: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 2, value: 3 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
# Use many registers to create pressure
|
||||
- "Move { dest: 23, src: 11 }"
|
||||
- "Move { dest: 24, src: 21 }"
|
||||
- "Move { dest: 25, src: 23 }"
|
||||
- "Move { dest: 26, src: 24 }"
|
||||
- "Add { dest: 27, left: 25, right: 26 }"
|
||||
- "Load { dest: 28, literal_idx: 2 }"
|
||||
- "Gt { dest: 29, left: 27, right: 28 }"
|
||||
- "AssertCondition { condition: 29 }"
|
||||
- "LoopNext { body_start: 6, loop_end: 14 }"
|
||||
- "AssertCondition { condition: 22 }"
|
||||
- "LoopNext { body_start: 3, loop_end: 16 }"
|
||||
- "Return { value: 12 }"
|
||||
want_result: true
|
||||
277
tests/rvm/vm/suites/default_rules.yaml
Normal file
277
tests/rvm/vm/suites/default_rules.yaml
Normal file
@@ -0,0 +1,277 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Default Rules Infrastructure Test Suite
|
||||
# Tests the VM infrastructure support for default rule evaluation
|
||||
# These tests verify that the VM can properly handle complete rules with default fallbacks
|
||||
|
||||
cases:
|
||||
- note: vm_default_rule_boolean_false
|
||||
description: Test VM default rule evaluation with boolean false value
|
||||
example_rego: "default allow := false; allow := true if { false }"
|
||||
literals:
|
||||
- false
|
||||
- true
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (false)
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 0 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load true result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: false # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_boolean_true
|
||||
description: Test VM default rule evaluation with boolean true value
|
||||
example_rego: "default enabled := true; enabled := false if { false }"
|
||||
literals:
|
||||
- true
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (true)
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load false result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: true # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_string_value
|
||||
description: Test VM default rule evaluation with string value
|
||||
example_rego: "default message := \"default\"; message := \"success\" if { false }"
|
||||
literals:
|
||||
- "default"
|
||||
- "success"
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 ("default")
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load success result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: "default" # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_number_value
|
||||
description: Test VM default rule evaluation with numeric value
|
||||
example_rego: "default count := 0; count := 10 if { false }"
|
||||
literals:
|
||||
- 0
|
||||
- 10
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (0)
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load 10 result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: 0 # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_array_value
|
||||
description: Test VM default rule evaluation with array value
|
||||
example_rego: "default items := [\"default\"]; items := [\"success\"] if { false }"
|
||||
literals:
|
||||
- ["default"]
|
||||
- ["success"]
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (["default"])
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load success array
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: ["default"] # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_object_value
|
||||
description: Test VM default rule evaluation with object value
|
||||
example_rego: "default config := {\"mode\": \"safe\"}; config := {\"mode\": \"fast\"} if { false }"
|
||||
literals:
|
||||
- {"mode": "safe"}
|
||||
- {"mode": "fast"}
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 ({"mode": "safe"})
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load fast config
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result:
|
||||
mode: "safe" # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_null_value
|
||||
description: Test VM default rule evaluation with null value
|
||||
example_rego: "default optional := null; optional := \"value\" if { false }"
|
||||
literals:
|
||||
- null
|
||||
- "value"
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (null)
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load string value
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: null # Should return default value when condition fails
|
||||
|
||||
- note: vm_default_rule_multiple_definitions_fail
|
||||
description: Test VM default rule when multiple definitions all fail
|
||||
example_rego: "default result := \"fallback\"; result := \"first\" if { false }; result := \"second\" if { false }"
|
||||
literals:
|
||||
- "fallback"
|
||||
- "first"
|
||||
- "second"
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2], [7]] # Two rule definitions
|
||||
default_literal_index: 0 # Points to literal index 0 ("fallback")
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
# First definition
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load first result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
# Second definition
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 4, literal_idx: 3 }" # Load false condition
|
||||
- "AssertCondition { condition: 4 }" # Assert condition (will fail)
|
||||
- "Load { dest: 5, literal_idx: 2 }" # Load second result
|
||||
- "Move { dest: 1, src: 5 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: "fallback" # Should return default value when all definitions fail
|
||||
|
||||
- note: vm_default_rule_successful_condition
|
||||
description: Test VM rule evaluation when condition succeeds (should not use default)
|
||||
example_rego: "default allow := false; allow := true if { true }"
|
||||
literals:
|
||||
- false
|
||||
- true
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (false) - should not be used
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load true condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will succeed)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load true result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: true # Should return successful rule result, not default
|
||||
|
||||
- note: vm_default_rule_complex_object
|
||||
description: Test VM default rule with complex nested object
|
||||
example_rego: "default settings := complex_default; settings := complex_success if { false }"
|
||||
literals:
|
||||
- {
|
||||
"timeout": 30,
|
||||
"retries": 3,
|
||||
"features": {
|
||||
"logging": true,
|
||||
"monitoring": false
|
||||
},
|
||||
"endpoints": ["api1", "api2"]
|
||||
}
|
||||
- {
|
||||
"timeout": 60,
|
||||
"retries": 5,
|
||||
"features": {
|
||||
"logging": false,
|
||||
"monitoring": true
|
||||
},
|
||||
"endpoints": ["api3", "api4"]
|
||||
}
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
default_literal_index: 0 # Points to literal index 0 (complex default object)
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load success object
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result:
|
||||
timeout: 30
|
||||
retries: 3
|
||||
features:
|
||||
logging: true
|
||||
monitoring: false
|
||||
endpoints: ["api1", "api2"] # Should return default complex object
|
||||
|
||||
- note: vm_no_default_rule_undefined
|
||||
description: Test VM rule without default returns undefined when definition fails
|
||||
example_rego: "allow := true if { false } # No default rule"
|
||||
literals:
|
||||
- true
|
||||
- false
|
||||
rule_infos:
|
||||
- rule_type: "Complete"
|
||||
definitions: [[2]] # Rule definition starts at instruction 2
|
||||
# No default_literal_index - should return undefined
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }" # Call the complete rule
|
||||
- "Return { value: 0 }" # Return result
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }" # Initialize rule execution
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load false condition
|
||||
- "AssertCondition { condition: 2 }" # Assert condition (will fail)
|
||||
- "Load { dest: 3, literal_idx: 0 }" # Load true result
|
||||
- "Move { dest: 1, src: 3 }" # Move result to result register
|
||||
- "RuleReturn" # Return from rule
|
||||
want_result: "#undefined" # Should return undefined when no default and definition fails
|
||||
41
tests/rvm/vm/suites/destructuring_rules.yaml
Normal file
41
tests/rvm/vm/suites/destructuring_rules.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
# Destructuring Rules Test Suite
|
||||
# Covers DestructuringSuccess control flow for rule parameters.
|
||||
|
||||
cases:
|
||||
- note: destructuring_success_path
|
||||
description: Destructuring block passes and body executes
|
||||
literals:
|
||||
- 1
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [4]
|
||||
destructuring_blocks: [2]
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "DestructuringSuccess {}"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: 1
|
||||
|
||||
- note: destructuring_failure_skips_body
|
||||
description: Missing DestructuringSuccess causes undefined result
|
||||
literals:
|
||||
- 1
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [4]
|
||||
destructuring_blocks: [2]
|
||||
instructions:
|
||||
- "CallRule { dest: 0, rule_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
- "LoadFalse { dest: 2 }"
|
||||
- "AssertCondition { condition: 2 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: "#undefined"
|
||||
371
tests/rvm/vm/suites/function_calls.yaml
Normal file
371
tests/rvm/vm/suites/function_calls.yaml
Normal file
@@ -0,0 +1,371 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Function Call Instructions Test Suite
|
||||
# Tests the VM's function call mechanism with user-defined function rules
|
||||
# Covers FunctionCall instruction, argument passing, and return values
|
||||
|
||||
cases:
|
||||
# Basic Function Call Tests
|
||||
- note: simple_function_call
|
||||
description: Test basic function call instruction with one argument
|
||||
example_rego: "add_ten(5) where add_ten(x) := x + 10"
|
||||
literals:
|
||||
- {}
|
||||
- 5 # Argument value
|
||||
- 10 # Constant 10 for addition
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 6
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
function_call_params:
|
||||
- func: 0 # Rule index for add_ten function (rule 0)
|
||||
dest: 2 # Destination register for result
|
||||
args: [1] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [3] # Entry point for add_ten function body (PC 3)
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Load argument 5 into register 1
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 2 }" # Return result
|
||||
# Function body starts at PC 3 (entry point)
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load constant 10 into register 3
|
||||
- "Add { dest: 0, left: 1, right: 3 }" # Add argument + 10 (result in register 0)
|
||||
- "RuleReturn {}" # Return from function
|
||||
want_result: 15
|
||||
|
||||
- note: function_call_multiple_args
|
||||
description: Test function call with multiple arguments
|
||||
example_rego: "add(7, 3) where add(x, y) := x + y"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for add function
|
||||
- 7 # First argument
|
||||
- 3 # Second argument
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 3 # Destination register for result
|
||||
args: [1, 2] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [5] # Entry point for add function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load first argument
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load second argument
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 3 }" # Return result
|
||||
# Function body
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Add { dest: 0, left: 1, right: 2 }" # Add arguments (result in register 0)
|
||||
- "RuleReturn {}" # Return from function
|
||||
want_result: 10
|
||||
|
||||
- note: function_call_no_args
|
||||
description: Test function call with no arguments
|
||||
example_rego: "get_constant() where get_constant() := 42"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for get_constant function
|
||||
- 42 # Constant value
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 1 # Destination register for result
|
||||
args: [] # No arguments
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [3] # Entry point for get_constant function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 1 }" # Return result
|
||||
# Function body
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Load { dest: 0, literal_idx: 2 }" # Load constant 42 (result in register 0)
|
||||
- "RuleReturn {}" # Return from function
|
||||
want_result: 42
|
||||
|
||||
- note: function_call_with_multiplication
|
||||
description: Test function that performs multiplication
|
||||
example_rego: "square(6) where square(x) := x * x"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for square function
|
||||
- 6 # Argument value
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 2 # Destination register for result
|
||||
args: [1] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [4] # Entry point for square function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load argument 6
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 2 }" # Return result
|
||||
# Function body
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Mul { dest: 0, left: 1, right: 1 }" # Multiply x * x (result in register 0)
|
||||
- "RuleReturn {}" # Return from function
|
||||
want_result: 36
|
||||
|
||||
- note: nested_function_calls
|
||||
description: Test nested function calls
|
||||
example_rego: "double(add_one(5)) where double(x) := x * 2; add_one(x) := x + 1"
|
||||
literals:
|
||||
- {}
|
||||
- 1 # Rule index for add_one function
|
||||
- 0 # Rule index for double function
|
||||
- 5 # Initial argument
|
||||
- 1 # Constant 1
|
||||
- 2 # Constant 2
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 1 # First call: add_one (rule index 1)
|
||||
dest: 4 # Temporary result
|
||||
args: [2] # Argument register
|
||||
- func: 0 # Second call: double (rule index 0)
|
||||
dest: 5 # Final result
|
||||
args: [4] # Use result from first call
|
||||
rule_infos:
|
||||
- rule_type: Complete # double function
|
||||
definitions:
|
||||
- [10] # Entry point for double function body (updated)
|
||||
- rule_type: Complete # add_one function
|
||||
definitions:
|
||||
- [6] # Entry point for add_one function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load add_one rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load double rule index
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load argument 5
|
||||
- "FunctionCall { params_index: 0 }" # Call add_one(5)
|
||||
- "FunctionCall { params_index: 1 }" # Call double(result)
|
||||
- "Return { value: 5 }" # Return final result
|
||||
# add_one function body (starts at PC 6)
|
||||
- "RuleInit { result_reg: 0, rule_index: 1 }" # Initialize result register for add_one
|
||||
- "Load { dest: 3, literal_idx: 4 }" # Load constant 1
|
||||
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 (result in register 0)
|
||||
- "RuleReturn {}" # Return result
|
||||
# double function body (starts at PC 10)
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register for double
|
||||
- "Load { dest: 3, literal_idx: 5 }" # Load constant 2
|
||||
- "Mul { dest: 0, left: 1, right: 3 }" # x * 2 (result in register 0)
|
||||
- "RuleReturn {}" # Return result
|
||||
want_result: 12
|
||||
|
||||
- note: function_call_undefined_result
|
||||
description: Test function call that returns undefined due to failed condition
|
||||
example_rego: "safe_div(1, 0) where safe_div(x, y) := x / y if y != 0"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for safe_div function
|
||||
- 1 # Numerator
|
||||
- 0 # Denominator (zero)
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 3 # Destination register for result
|
||||
args: [1, 2] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [5] # Entry point for safe_div function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load numerator
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load denominator
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 3 }" # Return result
|
||||
# Function body with condition that fails
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "LoadFalse { dest: 4 }" # Condition fails (simulated y == 0)
|
||||
- "AssertCondition { condition: 4 }" # Assert fails, body fails
|
||||
- "Div { dest: 0, left: 1, right: 2 }" # This won't execute
|
||||
- "RuleReturn {}" # This won't execute
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: function_call_with_object_result
|
||||
description: Test function call that returns an object
|
||||
example_rego: "make_pair(1, 2) where make_pair(x, y) := {\"first\": x, \"second\": y}"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for make_pair function
|
||||
- 1 # First value
|
||||
- 2 # Second value
|
||||
- "first" # Key for first element
|
||||
- "second" # Key for second element
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 6
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 5 # Destination register for result
|
||||
args: [1, 2] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [5] # Entry point for make_pair function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load first value
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load second value
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 5 }" # Return result
|
||||
# Function body
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 4 }" # Load "first" key
|
||||
- "ObjectSet { obj: 6, key: 3, value: 1 }" # Set first: x
|
||||
- "Load { dest: 4, literal_idx: 5 }" # Load "second" key
|
||||
- "ObjectSet { obj: 6, key: 4, value: 2 }" # Set second: y
|
||||
- "Move { dest: 0, src: 6 }" # Move object to result register
|
||||
- "RuleReturn {}" # Return object
|
||||
want_result: {"first": 1, "second": 2}
|
||||
|
||||
- note: function_call_with_array_result
|
||||
description: Test function call that returns an array
|
||||
example_rego: "make_range(3, 5) where make_range(start, end) := [start, end]"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for make_range function
|
||||
- 3 # Start value
|
||||
- 5 # End value
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 3 # Destination register for result
|
||||
args: [1, 2] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [5] # Entry point for make_range function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load start value
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load end value
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 3 }" # Return result
|
||||
# Function body
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "ArrayNew { dest: 4 }" # Create new array
|
||||
- "ArrayPush { arr: 4, value: 1 }" # Push start value
|
||||
- "ArrayPush { arr: 4, value: 2 }" # Push end value
|
||||
- "Move { dest: 0, src: 4 }" # Move array to result register
|
||||
- "RuleReturn {}" # Return array
|
||||
want_result: [3, 5]
|
||||
|
||||
- note: function_call_with_comparison
|
||||
description: Test function that performs comparison
|
||||
example_rego: "max(7, 3) where max(x, y) := x if x >= y"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for max function
|
||||
- 7 # First value
|
||||
- 3 # Second value
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 3 # Destination register for result
|
||||
args: [1, 2] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [5] # Entry point for max function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load first value (7)
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load second value (3)
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 3 }" # Return result
|
||||
# Function body: return x if x >= y
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Ge { dest: 4, left: 1, right: 2 }" # x >= y
|
||||
- "AssertCondition { condition: 4 }" # Assert condition
|
||||
- "Move { dest: 0, src: 1 }" # Return x (move to result register)
|
||||
- "RuleReturn {}" # Return result
|
||||
want_result: 7
|
||||
|
||||
- note: function_call_three_args
|
||||
description: Test function call with three arguments
|
||||
example_rego: "sum_three(2, 3, 4) where sum_three(a, b, c) := a + b + c"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for sum_three function
|
||||
- 2 # First argument
|
||||
- 3 # Second argument
|
||||
- 4 # Third argument
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 4 # Destination register for result
|
||||
args: [1, 2, 3] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [6] # Entry point for sum_three function body
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load first argument (2)
|
||||
- "Load { dest: 2, literal_idx: 3 }" # Load second argument (3)
|
||||
- "Load { dest: 3, literal_idx: 4 }" # Load third argument (4)
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 4 }" # Return result
|
||||
# Function body: a + b + c
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Add { dest: 5, left: 1, right: 2 }" # a + b
|
||||
- "Add { dest: 0, left: 5, right: 3 }" # (a + b) + c (result in register 0)
|
||||
- "RuleReturn {}" # Return result
|
||||
want_result: 9
|
||||
|
||||
- note: function_call_inconsistent_definitions
|
||||
description: Test function with multiple definitions that produce different values (should fail)
|
||||
example_rego: "f(5) where f(x) := x + 1; f(x) := x + 2"
|
||||
literals:
|
||||
- {}
|
||||
- 0 # Rule index for f function
|
||||
- 5 # Argument value
|
||||
- 1 # Constant 1
|
||||
- 2 # Constant 2
|
||||
instruction_params:
|
||||
function_call_params:
|
||||
- func: 0 # Register containing function rule index
|
||||
dest: 2 # Destination register for result
|
||||
args: [1] # Argument registers
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [4] # First definition: x + 1
|
||||
- [8] # Second definition: x + 2 (should produce different result)
|
||||
entry_points:
|
||||
"data.test.compute": [4, 8] # Multiple definitions for the same function
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 1 }" # Load rule index
|
||||
- "Load { dest: 1, literal_idx: 2 }" # Load argument (5)
|
||||
- "FunctionCall { params_index: 0 }" # Call function
|
||||
- "Return { value: 2 }" # Return result
|
||||
# First definition: x + 1 (entry point 4)
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Load { dest: 3, literal_idx: 3 }" # Load constant 1
|
||||
- "Add { dest: 0, left: 1, right: 3 }" # x + 1 = 6 (result in register 0)
|
||||
- "RuleReturn {}" # Return result
|
||||
# Second definition: x + 2 (entry point 8)
|
||||
- "RuleInit { result_reg: 0, rule_index: 0 }" # Initialize result register
|
||||
- "Load { dest: 4, literal_idx: 4 }" # Load constant 2
|
||||
- "Add { dest: 0, left: 1, right: 4 }" # x + 2 = 7 (result in register 0)
|
||||
- "RuleReturn {}" # Return result
|
||||
want_result: "#undefined"
|
||||
14
tests/rvm/vm/suites/halt.yaml
Normal file
14
tests/rvm/vm/suites/halt.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
# Halt Instruction Test Suite
|
||||
# Ensures Halt returns register 0 and ignores trailing instructions.
|
||||
|
||||
cases:
|
||||
- note: halt_returns_register_zero
|
||||
description: Halt ends execution and returns register 0
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Halt"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: 42
|
||||
137
tests/rvm/vm/suites/host_await.yaml
Normal file
137
tests/rvm/vm/suites/host_await.yaml
Normal file
@@ -0,0 +1,137 @@
|
||||
# HostAwait integration test suite
|
||||
# Verifies host suspension across execution modes and optional run-to-completion fallbacks.
|
||||
|
||||
cases:
|
||||
- note: host_await_single_response
|
||||
description: Single HostAwait resumes with provided response in every execution mode
|
||||
literals:
|
||||
- "ping"
|
||||
- "await-0"
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 2, arg: 1, id: 3 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses:
|
||||
- id: "await-0"
|
||||
value: "pong"
|
||||
want_result:
|
||||
- "pong"
|
||||
|
||||
- note: host_await_multiple_responses
|
||||
description: Multiple HostAwait instructions consume a queued sequence of responses
|
||||
literals:
|
||||
- 7
|
||||
- "await-first"
|
||||
- "await-second"
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 2, arg: 1, id: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "HostAwait { dest: 3, arg: 2, id: 5 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses_run_to_completion:
|
||||
- id: "await-first"
|
||||
value: 42
|
||||
- id: "await-second"
|
||||
value: 43
|
||||
host_await_responses_suspendable:
|
||||
- id: "await-first"
|
||||
value: 42
|
||||
- id: "await-second"
|
||||
value: 43
|
||||
want_result:
|
||||
- 42
|
||||
- 43
|
||||
|
||||
- note: host_await_suspendable_only
|
||||
description: Run-to-completion failure due to missing HostAwait response is ignored when flagged
|
||||
literals:
|
||||
- "payload"
|
||||
- "await-single"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "HostAwait { dest: 1, arg: 0, id: 2 }"
|
||||
- "Return { value: 1 }"
|
||||
host_await_responses_suspendable:
|
||||
- id: "await-single"
|
||||
value: "resume-value"
|
||||
ignore_run_to_completion_hostawait_failure: true
|
||||
want_result: "resume-value"
|
||||
|
||||
- note: host_await_multiple_sequential
|
||||
description: Three sequential HostAwait calls with different responses
|
||||
literals:
|
||||
- "first"
|
||||
- "second"
|
||||
- "third"
|
||||
- "id-1"
|
||||
- "id-2"
|
||||
- "id-3"
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 10, literal_idx: 3 }"
|
||||
- "HostAwait { dest: 2, arg: 1, id: 10 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Load { dest: 11, literal_idx: 4 }"
|
||||
- "HostAwait { dest: 4, arg: 3, id: 11 }"
|
||||
- "ArrayPush { arr: 0, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "Load { dest: 12, literal_idx: 5 }"
|
||||
- "HostAwait { dest: 6, arg: 5, id: 12 }"
|
||||
- "ArrayPush { arr: 0, value: 6 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses:
|
||||
- id: "id-1"
|
||||
value: "response-1"
|
||||
- id: "id-2"
|
||||
value: "response-2"
|
||||
- id: "id-3"
|
||||
value: "response-3"
|
||||
want_result: ["response-1", "response-2", "response-3"]
|
||||
|
||||
- note: host_await_in_loop_body
|
||||
description: HostAwait inside loop body - suspend/resume per iteration
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- "id-1"
|
||||
- "id-2"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 5
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 7
|
||||
loop_end: 11
|
||||
instructions:
|
||||
- "ArrayNew { dest: 5 }" # Collection [1, 2]
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 5, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 2 }"
|
||||
- "ArrayNew { dest: 0 }" # Result array
|
||||
- "LoopStart { params_index: 0 }"
|
||||
# HostAwait with dynamic ID based on loop value
|
||||
- "Load { dest: 13, literal_idx: 2 }" # Load base ID
|
||||
- "HostAwait { dest: 14, arg: 11, id: 13 }"
|
||||
- "ArrayPush { arr: 0, value: 14 }"
|
||||
- "LoopNext { body_start: 7, loop_end: 11 }"
|
||||
- "Return { value: 0 }"
|
||||
host_await_responses:
|
||||
- id: "id-1"
|
||||
value: "loop-response-1"
|
||||
- id: "id-1"
|
||||
value: "loop-response-2"
|
||||
want_result: ["loop-response-1", "loop-response-2"]
|
||||
28
tests/rvm/vm/suites/host_await_failures.yaml
Normal file
28
tests/rvm/vm/suites/host_await_failures.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Host Await Failures Test Suite
|
||||
# Validates HostAwait error surfaces under different execution modes.
|
||||
|
||||
cases:
|
||||
- note: host_await_missing_response
|
||||
description: Run-to-completion mode errors when responses absent
|
||||
literals:
|
||||
- "request"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "HostAwait { dest: 1, arg: 0, id: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_error: "HostAwait executed but no response provided"
|
||||
|
||||
- note: host_await_ignore_flag
|
||||
description: ignore_run_to_completion_hostawait_failure guards missing responses
|
||||
literals:
|
||||
- "request"
|
||||
- "response"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "HostAwait { dest: 1, arg: 0, id: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
host_await_responses_suspendable:
|
||||
- id: "request"
|
||||
value: "response"
|
||||
ignore_run_to_completion_hostawait_failure: true
|
||||
want_result: "response"
|
||||
241
tests/rvm/vm/suites/indexed_access.yaml
Normal file
241
tests/rvm/vm/suites/indexed_access.yaml
Normal file
@@ -0,0 +1,241 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Indexed Access Test Suite
|
||||
# Exercises literal indexing, chained indexing, and error paths.
|
||||
|
||||
cases:
|
||||
- note: index_literal_success
|
||||
description: IndexLiteral retrieves value when literal key exists
|
||||
literals:
|
||||
- { key: true }
|
||||
- "key"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "LoadTrue { dest: 2 }"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "IndexLiteral { dest: 3, container: 0, literal_idx: 1 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: true
|
||||
|
||||
- note: index_literal_bad_literal
|
||||
description: IndexLiteral with invalid literal index triggers error
|
||||
literals:
|
||||
- {}
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "IndexLiteral { dest: 1, container: 0, literal_idx: 99 }"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
want_error: "Literal index 99 out of bounds"
|
||||
|
||||
- note: chained_index_mixed_path
|
||||
description: ChainedIndex handles literal and register path components
|
||||
literals:
|
||||
- {}
|
||||
- "outer"
|
||||
- "inner"
|
||||
- "value"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
chained_index_params:
|
||||
- dest: 5
|
||||
root: 0
|
||||
path_components:
|
||||
- literal_idx: 1
|
||||
- register: 3
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }" # inner
|
||||
- "Load { dest: 4, literal_idx: 3 }" # value
|
||||
- "Load { dest: 2, literal_idx: 0 }" # nested object builder
|
||||
- "ObjectSet { obj: 2, key: 3, value: 4 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # outer
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: "value"
|
||||
|
||||
- note: chained_index_undefined_propagation
|
||||
description: ChainedIndex yields undefined when intermediate missing
|
||||
literals:
|
||||
- {}
|
||||
- "missing"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
chained_index_params:
|
||||
- dest: 2
|
||||
root: 0
|
||||
path_components:
|
||||
- literal_idx: 1
|
||||
- literal_idx: 1
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: chained_index_5_components
|
||||
description: ChainedIndex with 5 path components
|
||||
example_rego: "data.a.b.c.d.e"
|
||||
literals:
|
||||
- {}
|
||||
- "a"
|
||||
- "b"
|
||||
- "c"
|
||||
- "d"
|
||||
- "e"
|
||||
- "final_value"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
chained_index_params:
|
||||
- dest: 10
|
||||
root: 0
|
||||
path_components:
|
||||
- literal_idx: 1 # a
|
||||
- literal_idx: 2 # b
|
||||
- literal_idx: 3 # c
|
||||
- literal_idx: 4 # d
|
||||
- literal_idx: 5 # e
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }" # r0 = {}
|
||||
# Build nested structure: {a: {b: {c: {d: {e: "final_value"}}}}}
|
||||
- "Load { dest: 1, literal_idx: 0 }" # innermost object
|
||||
- "Load { dest: 2, literal_idx: 5 }" # key "e"
|
||||
- "Load { dest: 3, literal_idx: 6 }" # value "final_value"
|
||||
- "ObjectSet { obj: 1, key: 2, value: 3 }" # {e: "final_value"}
|
||||
- "Load { dest: 4, literal_idx: 0 }" # d level object
|
||||
- "Load { dest: 5, literal_idx: 4 }" # key "d"
|
||||
- "ObjectSet { obj: 4, key: 5, value: 1 }" # {d: {e: ...}}
|
||||
- "Load { dest: 6, literal_idx: 0 }" # c level object
|
||||
- "Load { dest: 7, literal_idx: 3 }" # key "c"
|
||||
- "ObjectSet { obj: 6, key: 7, value: 4 }" # {c: {d: ...}}
|
||||
- "Load { dest: 8, literal_idx: 0 }" # b level object
|
||||
- "Load { dest: 9, literal_idx: 2 }" # key "b"
|
||||
- "ObjectSet { obj: 8, key: 9, value: 6 }" # {b: {c: ...}}
|
||||
- "Load { dest: 11, literal_idx: 1 }" # key "a"
|
||||
- "ObjectSet { obj: 0, key: 11, value: 8 }" # {a: {b: ...}}
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 10 }"
|
||||
want_result: "final_value"
|
||||
|
||||
- note: chained_index_all_registers
|
||||
description: ChainedIndex with all register components (no literals)
|
||||
example_rego: "obj[key1][key2]"
|
||||
literals:
|
||||
- {}
|
||||
- "level1"
|
||||
- "level2"
|
||||
- "result"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
chained_index_params:
|
||||
- dest: 10
|
||||
root: 0
|
||||
path_components:
|
||||
- register: 5 # key1
|
||||
- register: 6 # key2
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }" # r0 = {}
|
||||
# Build structure: {level1: {level2: "result"}}
|
||||
- "Load { dest: 1, literal_idx: 0 }" # inner object
|
||||
- "Load { dest: 2, literal_idx: 2 }" # "level2"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # "result"
|
||||
- "ObjectSet { obj: 1, key: 2, value: 3 }" # {level2: "result"}
|
||||
- "Load { dest: 4, literal_idx: 1 }" # "level1"
|
||||
- "ObjectSet { obj: 0, key: 4, value: 1 }" # {level1: {...}}
|
||||
# Set up dynamic keys in registers
|
||||
- "Load { dest: 5, literal_idx: 1 }" # key1 = "level1"
|
||||
- "Load { dest: 6, literal_idx: 2 }" # key2 = "level2"
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 10 }"
|
||||
want_result: "result"
|
||||
|
||||
- note: index_with_null_key
|
||||
description: Index with null key returns undefined
|
||||
example_rego: "obj[null]"
|
||||
literals:
|
||||
- {"key": "value"}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: index_with_undefined_key
|
||||
description: Index with undefined key returns undefined
|
||||
example_rego: "obj[undefined_var]"
|
||||
literals:
|
||||
- {"key": "value"}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
# r1 is undefined (never loaded)
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: index_with_boolean_key
|
||||
description: Index with boolean key
|
||||
example_rego: "obj[true]"
|
||||
literals:
|
||||
- {true: "bool_value", "regular": "string_value"}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "bool_value"
|
||||
|
||||
- note: index_with_empty_string_key
|
||||
description: Index with empty string key
|
||||
example_rego: "obj[\"\"]"
|
||||
literals:
|
||||
- {"": "empty_key_value", "other": "other_value"}
|
||||
- ""
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "empty_key_value"
|
||||
|
||||
- note: index_with_number_key_on_object
|
||||
description: Index object with number key
|
||||
example_rego: "obj[42]"
|
||||
literals:
|
||||
- {42: "number_value", "str": "string_value"}
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "number_value"
|
||||
432
tests/rvm/vm/suites/integration_scenarios.yaml
Normal file
432
tests/rvm/vm/suites/integration_scenarios.yaml
Normal file
@@ -0,0 +1,432 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Integration Scenarios Test Suite
|
||||
# Tests real-world Rego policy patterns
|
||||
# Covers RBAC, filtering, transforms, and complex policy workflows
|
||||
|
||||
cases:
|
||||
- note: rbac_admin_check
|
||||
description: Role-based access control - check admin role
|
||||
example_rego: "allow { user.roles[_] == \"admin\" }"
|
||||
literals:
|
||||
- {"roles": ["user", "admin", "developer"]}
|
||||
- "roles"
|
||||
- "admin"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 2
|
||||
key_reg: 3
|
||||
value_reg: 4
|
||||
result_reg: 6
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Eq { dest: 7, left: 4, right: 5 }"
|
||||
- "AssertCondition { condition: 7 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: true
|
||||
|
||||
- note: rbac_no_matching_role
|
||||
description: RBAC check fails when role not present
|
||||
example_rego: "allow { user.roles[_] == \"superadmin\" }"
|
||||
literals:
|
||||
- {"roles": ["user", "developer"]}
|
||||
- "roles"
|
||||
- "superadmin"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 2
|
||||
key_reg: 3
|
||||
value_reg: 4
|
||||
result_reg: 6
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Eq { dest: 7, left: 4, right: 5 }"
|
||||
- "AssertCondition { condition: 7 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: false
|
||||
|
||||
- note: filtering_by_owner
|
||||
description: Filter resources by owner ID
|
||||
example_rego: "[x | x = resources[_]; x.owner == user.id]"
|
||||
literals:
|
||||
- [{"name": "res1", "owner": "user123"}, {"name": "res2", "owner": "user456"}, {"name": "res3", "owner": "user123"}]
|
||||
- {"id": "user123"}
|
||||
- "owner"
|
||||
- "id"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 11
|
||||
body_start: 4
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ArrayNew { dest: 8 }"
|
||||
- "Load { dest: 9, literal_idx: 1 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "Index { dest: 6, container: 9, key: 5 }"
|
||||
- "Eq { dest: 7, left: 4, right: 6 }"
|
||||
- "AssertCondition { condition: 7 }"
|
||||
- "ArrayPush { arr: 8, value: 2 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 12 }"
|
||||
- "Return { value: 8 }"
|
||||
want_result: [{"name": "res1", "owner": "user123"}, {"name": "res3", "owner": "user123"}]
|
||||
|
||||
- note: filtering_empty_result
|
||||
description: Filter returns empty array when no matches
|
||||
example_rego: "[x | x = resources[_]; x.owner == \"nonexistent\"]"
|
||||
literals:
|
||||
- [{"name": "res1", "owner": "user123"}, {"name": "res2", "owner": "user456"}]
|
||||
- "owner"
|
||||
- "nonexistent"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 8
|
||||
body_start: 4
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ArrayNew { dest: 7 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Eq { dest: 6, left: 4, right: 5 }"
|
||||
- "AssertCondition { condition: 6 }"
|
||||
- "ArrayPush { arr: 7, value: 2 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 10 }"
|
||||
- "Return { value: 7 }"
|
||||
want_result: []
|
||||
|
||||
- note: transform_multiply_values
|
||||
description: Transform values by multiplying above threshold
|
||||
example_rego: "{k: v * 2 | v = data.metrics[k]; v > threshold}"
|
||||
literals:
|
||||
- {"cpu": 50, "memory": 80, "disk": 30}
|
||||
- 40
|
||||
- 2
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 4
|
||||
template_literal_idx: 3
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 9
|
||||
body_start: 4
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Gt { dest: 5, left: 2, right: 3 }"
|
||||
- "AssertCondition { condition: 5 }"
|
||||
- "Load { dest: 7, literal_idx: 2 }"
|
||||
- "Mul { dest: 8, left: 2, right: 7 }"
|
||||
- "ObjectSet { obj: 4, key: 1, value: 8 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 10 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: {"cpu": 100, "memory": 160}
|
||||
|
||||
- note: transform_all_values
|
||||
description: Transform all values in map
|
||||
example_rego: "{k: v + 10 | v = data[k]}"
|
||||
literals:
|
||||
- {"a": 1, "b": 2, "c": 3}
|
||||
- 10
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 4
|
||||
template_literal_idx: 2
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 6
|
||||
body_start: 4
|
||||
loop_end: 7
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Add { dest: 5, left: 2, right: 3 }"
|
||||
- "ObjectSet { obj: 4, key: 1, value: 5 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 7 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: {"a": 11, "b": 12, "c": 13}
|
||||
|
||||
- note: nested_filtering_and_transform
|
||||
description: Filter then transform (chained operations)
|
||||
example_rego: "[x * 2 | x = numbers[_]; x > 5]"
|
||||
literals:
|
||||
- [3, 7, 4, 9, 2, 8]
|
||||
- 5
|
||||
- 2
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 6
|
||||
body_start: 5
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ArrayNew { dest: 5 }"
|
||||
- "Load { dest: 9, literal_idx: 1 }"
|
||||
- "Load { dest: 10, literal_idx: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Gt { dest: 3, left: 2, right: 9 }"
|
||||
- "AssertCondition { condition: 3 }"
|
||||
- "Mul { dest: 4, left: 2, right: 10 }"
|
||||
- "ArrayPush { arr: 5, value: 4 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 10 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: [14, 18, 16]
|
||||
|
||||
- note: multi_field_validation
|
||||
description: Validate multiple fields meet criteria
|
||||
example_rego: "valid { user.age >= 18; user.verified == true; user.status == \"active\" }"
|
||||
literals:
|
||||
- {"age": 25, "verified": true, "status": "active"}
|
||||
- "age"
|
||||
- 18
|
||||
- "verified"
|
||||
- "status"
|
||||
- "active"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
# Check age >= 18
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Ge { dest: 4, left: 2, right: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "Index { dest: 6, container: 0, key: 5 }"
|
||||
- "LoadTrue { dest: 7 }"
|
||||
- "Eq { dest: 8, left: 6, right: 7 }"
|
||||
- "And { dest: 9, left: 4, right: 8 }"
|
||||
- "Load { dest: 10, literal_idx: 4 }"
|
||||
- "Index { dest: 11, container: 0, key: 10 }"
|
||||
- "Load { dest: 12, literal_idx: 5 }"
|
||||
- "Eq { dest: 13, left: 11, right: 12 }"
|
||||
- "And { dest: 14, left: 9, right: 13 }"
|
||||
- "Return { value: 14 }"
|
||||
want_result: true
|
||||
|
||||
- note: multi_field_validation_failure
|
||||
description: Multi-field validation fails when one criterion not met
|
||||
example_rego: "valid { user.age >= 18; user.verified == true; user.status == \"active\" }"
|
||||
literals:
|
||||
- {"age": 25, "verified": false, "status": "active"}
|
||||
- "age"
|
||||
- 18
|
||||
- "verified"
|
||||
- "status"
|
||||
- "active"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
# Check age >= 18
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Ge { dest: 4, left: 2, right: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "Index { dest: 6, container: 0, key: 5 }"
|
||||
- "LoadTrue { dest: 7 }"
|
||||
- "Eq { dest: 8, left: 6, right: 7 }"
|
||||
- "And { dest: 9, left: 4, right: 8 }"
|
||||
- "Load { dest: 10, literal_idx: 4 }"
|
||||
- "Index { dest: 11, container: 0, key: 10 }"
|
||||
- "Load { dest: 12, literal_idx: 5 }"
|
||||
- "Eq { dest: 13, left: 11, right: 12 }"
|
||||
- "And { dest: 14, left: 9, right: 13 }"
|
||||
- "Return { value: 14 }"
|
||||
want_result: false
|
||||
|
||||
- note: set_membership_check
|
||||
description: Check if value is in allowed set
|
||||
example_rego: "allowed { input.action in {\"read\", \"write\", \"delete\"} }"
|
||||
literals:
|
||||
- {"action": "read"}
|
||||
- "action"
|
||||
- "read"
|
||||
- "write"
|
||||
- "delete"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
# Create allowed set
|
||||
- "SetNew { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "SetAdd { set: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "SetAdd { set: 3, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 4 }"
|
||||
- "SetAdd { set: 3, value: 6 }"
|
||||
# Check membership
|
||||
- "Contains { dest: 7, collection: 3, value: 2 }"
|
||||
- "Return { value: 7 }"
|
||||
want_result: true
|
||||
|
||||
- note: set_membership_denied
|
||||
description: Check fails when value not in allowed set
|
||||
example_rego: "allowed { input.action in {\"read\", \"write\", \"delete\"} }"
|
||||
literals:
|
||||
- {"action": "execute"}
|
||||
- "action"
|
||||
- "read"
|
||||
- "write"
|
||||
- "delete"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
# Create allowed set
|
||||
- "SetNew { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "SetAdd { set: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "SetAdd { set: 3, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 4 }"
|
||||
- "SetAdd { set: 3, value: 6 }"
|
||||
# Check membership
|
||||
- "Contains { dest: 7, collection: 3, value: 2 }"
|
||||
- "Return { value: 7 }"
|
||||
want_result: false
|
||||
|
||||
- note: aggregation_count
|
||||
description: Count items matching condition
|
||||
example_rego: "count([x | x = items[_]; x.status == \"active\"])"
|
||||
literals:
|
||||
- [{"id": 1, "status": "active"}, {"id": 2, "status": "inactive"}, {"id": 3, "status": "active"}]
|
||||
- "status"
|
||||
- "active"
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 8
|
||||
body_start: 4
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "Load { dest: 9, literal_idx: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Eq { dest: 5, left: 4, right: 9 }"
|
||||
- "AssertCondition { condition: 5 }"
|
||||
- "ArrayPush { arr: 6, value: 2 }"
|
||||
- "LoopNext { body_start: 4, loop_end: 10 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: [{"id": 1, "status": "active"}, {"id": 3, "status": "active"}]
|
||||
|
||||
- note: hierarchical_permission_check
|
||||
description: Check nested permission structure
|
||||
example_rego: "allow { data.permissions[input.user][input.resource] == \"allow\" }"
|
||||
literals:
|
||||
- {"permissions": {"alice": {"doc1": "allow", "doc2": "deny"}, "bob": {"doc1": "deny"}}}
|
||||
- {"user": "alice", "resource": "doc1"}
|
||||
- "permissions"
|
||||
- "user"
|
||||
- "resource"
|
||||
- "allow"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
# Load permissions
|
||||
- "Load { dest: 2, literal_idx: 2 }"
|
||||
- "Index { dest: 3, container: 0, key: 2 }"
|
||||
# Load user
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "Index { dest: 5, container: 1, key: 4 }"
|
||||
# Index permissions by user
|
||||
- "Index { dest: 6, container: 3, key: 5 }"
|
||||
# Load resource
|
||||
- "Load { dest: 7, literal_idx: 4 }"
|
||||
- "Index { dest: 8, container: 1, key: 7 }"
|
||||
# Index user permissions by resource
|
||||
- "Index { dest: 9, container: 6, key: 8 }"
|
||||
# Check if "allow"
|
||||
- "Load { dest: 10, literal_idx: 5 }"
|
||||
- "Eq { dest: 11, left: 9, right: 10 }"
|
||||
- "Return { value: 11 }"
|
||||
want_result: true
|
||||
|
||||
- note: default_value_pattern
|
||||
description: Provide default value when path undefined
|
||||
example_rego: "timeout := data.config.timeout; timeout == 30 if not data.config.timeout"
|
||||
literals:
|
||||
- {}
|
||||
- "config"
|
||||
- "timeout"
|
||||
- 30
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 7
|
||||
key_reg: 8
|
||||
value_reg: 9
|
||||
result_reg: 10
|
||||
body_start: 10
|
||||
loop_end: 13
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "Contains { dest: 6, collection: 2, value: 3 }"
|
||||
- "ArrayNew { dest: 7 }"
|
||||
- "ArrayPush { arr: 7, value: 6 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "AssertCondition { condition: 9 }"
|
||||
- "Move { dest: 5, src: 4 }"
|
||||
- "LoopNext { body_start: 10, loop_end: 13 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: 30
|
||||
351
tests/rvm/vm/suites/interpreter_operator_compatibility.yaml
Normal file
351
tests/rvm/vm/suites/interpreter_operator_compatibility.yaml
Normal file
@@ -0,0 +1,351 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Interpreter Operator Compatibility Test Suite
|
||||
# Tests that VM operators behave exactly like interpreter operators
|
||||
# Focuses on edge cases like undefined value handling, division by zero, etc.
|
||||
|
||||
cases:
|
||||
# Undefined Value Arithmetic Tests
|
||||
- note: undefined_add
|
||||
description: Test addition with undefined value
|
||||
example_rego: "input.nonexistent + 5"
|
||||
input: {} # Empty input to create undefined access
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }" # Load input into register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load string "nonexistent" for indexing
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 5 into register 2
|
||||
- "Index { dest: 3, container: 0, key: 1 }" # Access input.nonexistent (undefined)
|
||||
- "Add { dest: 4, left: 3, right: 2 }" # undefined + 5
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_sub
|
||||
description: Test subtraction with undefined value
|
||||
example_rego: "input.nonexistent - 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Sub { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_mul
|
||||
description: Test multiplication with undefined value
|
||||
example_rego: "input.nonexistent * 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Mul { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# Division by Zero Tests
|
||||
- note: division_by_zero
|
||||
description: Test division by zero returns undefined (non-strict mode)
|
||||
example_rego: "5 / 0"
|
||||
literals:
|
||||
- 5
|
||||
- 0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Div { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: modulo_by_zero
|
||||
description: Test modulo by zero returns undefined (non-strict mode)
|
||||
example_rego: "5 % 0"
|
||||
literals:
|
||||
- 5
|
||||
- 0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# Modulo Float Tests
|
||||
- note: modulo_float
|
||||
description: Test modulo with floating point numbers (should error)
|
||||
example_rego: "5.5 % 2"
|
||||
literals:
|
||||
- 5.5
|
||||
- 2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "modulo on floating-point number"
|
||||
|
||||
# Undefined Comparison Tests
|
||||
- note: undefined_eq
|
||||
description: Test equality comparison with undefined value
|
||||
example_rego: "input.nonexistent == 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Eq { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_ne
|
||||
description: Test inequality comparison with undefined value
|
||||
example_rego: "input.nonexistent != 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Ne { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_lt
|
||||
description: Test less than comparison with undefined value
|
||||
example_rego: "input.nonexistent < 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Lt { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_le
|
||||
description: Test less than or equal comparison with undefined value
|
||||
example_rego: "input.nonexistent <= 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Le { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_gt
|
||||
description: Test greater than comparison with undefined value
|
||||
example_rego: "input.nonexistent > 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Gt { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_ge
|
||||
description: Test greater than or equal comparison with undefined value
|
||||
example_rego: "input.nonexistent >= 5"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent"
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Ge { dest: 4, left: 3, right: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# Number Type Precision Tests
|
||||
- note: number_add_precision
|
||||
description: Test Number type addition preserves precision
|
||||
example_rego: "1.1 + 2.2"
|
||||
literals:
|
||||
- 1.1
|
||||
- 2.2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 3.3
|
||||
|
||||
- note: number_sub_precision
|
||||
description: Test Number type subtraction preserves precision
|
||||
example_rego: "5.5 - 2.2"
|
||||
literals:
|
||||
- 5.5
|
||||
- 2.2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Sub { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 3.3
|
||||
|
||||
- note: number_mul_precision
|
||||
description: Test Number type multiplication preserves precision
|
||||
example_rego: "2.5 * 4.0"
|
||||
literals:
|
||||
- 2.5
|
||||
- 4.0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mul { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 10.0
|
||||
|
||||
- note: number_div_precision
|
||||
description: Test Number type division preserves precision
|
||||
example_rego: "7.5 / 2.5"
|
||||
literals:
|
||||
- 7.5
|
||||
- 2.5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Div { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 3.0
|
||||
|
||||
# Integer Modulo Tests
|
||||
- note: integer_modulo
|
||||
description: Test integer modulo works correctly
|
||||
example_rego: "7 % 3"
|
||||
literals:
|
||||
- 7
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Mod { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 1
|
||||
|
||||
# Value Ordering Tests
|
||||
- note: value_ordering_null_bool
|
||||
description: Test null < bool ordering
|
||||
example_rego: "null < true"
|
||||
literals:
|
||||
- null
|
||||
- true
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Lt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: value_ordering_bool_number
|
||||
description: Test bool < number ordering
|
||||
example_rego: "true < 1"
|
||||
literals:
|
||||
- true
|
||||
- 1
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Lt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: value_ordering_number_string
|
||||
description: Test number < string ordering
|
||||
example_rego: "1 < \"a\""
|
||||
literals:
|
||||
- 1
|
||||
- "a"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Lt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
# Edge Cases for Mixed Undefined Operations
|
||||
- note: undefined_both_operands
|
||||
description: Test operation with both operands undefined
|
||||
example_rego: "input.nonexistent1 + input.nonexistent2"
|
||||
input: {}
|
||||
literals:
|
||||
- "nonexistent1"
|
||||
- "nonexistent2"
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 0, key: 1 }"
|
||||
- "Index { dest: 4, container: 0, key: 2 }"
|
||||
- "Add { dest: 5, left: 3, right: 4 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_right_operand_arithmetic
|
||||
description: Test arithmetic with right operand undefined
|
||||
example_rego: "5 + input.nonexistent"
|
||||
input: {}
|
||||
literals:
|
||||
- 5
|
||||
- "nonexistent"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadInput { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 1, key: 2 }"
|
||||
- "Add { dest: 4, left: 0, right: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_right_operand_comparison
|
||||
description: Test comparison with right operand undefined
|
||||
example_rego: "5 == input.nonexistent"
|
||||
input: {}
|
||||
literals:
|
||||
- 5
|
||||
- "nonexistent"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadInput { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Index { dest: 3, container: 1, key: 2 }"
|
||||
- "Eq { dest: 4, left: 0, right: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
61
tests/rvm/vm/suites/invalid_collection_ops.yaml
Normal file
61
tests/rvm/vm/suites/invalid_collection_ops.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
# Invalid Collection Operations Test Suite
|
||||
# Exercises error conditions for collection-specific instructions.
|
||||
|
||||
cases:
|
||||
- note: object_set_on_non_object
|
||||
description: ObjectSet on non-object register triggers error
|
||||
literals:
|
||||
- 1
|
||||
- "key"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 1 }"
|
||||
want_error: "Register 0 does not contain an object"
|
||||
|
||||
- note: array_push_on_non_array
|
||||
description: ArrayPush on scalar register fails
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
want_error: "Register 0 does not contain an array"
|
||||
|
||||
- note: set_add_on_non_set
|
||||
description: SetAdd on scalar register fails
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
want_error: "Register 0 does not contain a set"
|
||||
|
||||
- note: object_create_invalid_template
|
||||
description: ObjectCreate fails when template literal is not object
|
||||
literals:
|
||||
- []
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
want_error: "ObjectCreate: template is not an object"
|
||||
|
||||
- note: contains_on_scalar_returns_false
|
||||
description: Contains on scalar should return false
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Contains { dest: 2, collection: 0, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
250
tests/rvm/vm/suites/load_data_input.yaml
Normal file
250
tests/rvm/vm/suites/load_data_input.yaml
Normal file
@@ -0,0 +1,250 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Load Data and Input Test Suite
|
||||
# Tests LoadData and LoadInput instructions with various edge cases
|
||||
# Covers empty, nested, undefined values, and interaction with virtual data lookups
|
||||
|
||||
cases:
|
||||
- note: load_data_empty
|
||||
description: LoadData from empty data context
|
||||
example_rego: "data"
|
||||
data: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {}
|
||||
|
||||
- note: load_input_empty
|
||||
description: LoadInput from empty input context
|
||||
example_rego: "input"
|
||||
input: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {}
|
||||
|
||||
- note: load_data_with_values
|
||||
description: LoadData returns populated data object
|
||||
example_rego: "data"
|
||||
data: {}
|
||||
literals: []
|
||||
# This would require the test harness to support setting data
|
||||
# For now, testing structure only
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {} # Default empty since harness may not set data
|
||||
|
||||
- note: load_input_with_values
|
||||
description: LoadInput returns populated input object
|
||||
example_rego: "input"
|
||||
input: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {} # Default empty
|
||||
|
||||
- note: load_data_and_index
|
||||
description: LoadData followed by indexing
|
||||
example_rego: "data.users"
|
||||
data: {}
|
||||
literals:
|
||||
- "users"
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined" # users doesn't exist in empty data
|
||||
|
||||
- note: load_input_and_index
|
||||
description: LoadInput followed by indexing
|
||||
example_rego: "input.user.name"
|
||||
input: {}
|
||||
literals:
|
||||
- "user"
|
||||
- "name"
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Index { dest: 4, container: 2, key: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined" # user doesn't exist in empty input
|
||||
|
||||
- note: load_data_multiple_times
|
||||
description: LoadData can be called multiple times
|
||||
example_rego: "data == data"
|
||||
data: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "LoadData { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_input_multiple_times
|
||||
description: LoadInput can be called multiple times
|
||||
example_rego: "input == input"
|
||||
input: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "LoadInput { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_data_not_equal_to_input
|
||||
description: data and input are separate contexts
|
||||
example_rego: "data == input"
|
||||
data: {}
|
||||
input: {}
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "LoadInput { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true # Both empty {}
|
||||
|
||||
- note: load_data_in_loop
|
||||
description: LoadData inside loop body
|
||||
example_rego: "[ data | _ = [1, 2][_] ]"
|
||||
data: {}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2]
|
||||
loop_params:
|
||||
- mode: ForEach
|
||||
collection: 0
|
||||
key_reg: 3
|
||||
value_reg: 4
|
||||
result_reg: 7
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
# Loop body starts
|
||||
- "LoadData { dest: 5 }"
|
||||
- "ArrayPush { arr: 6, value: 5 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }"
|
||||
# Loop done
|
||||
- "Return { value: 6 }"
|
||||
want_result: [{}, {}]
|
||||
|
||||
- note: load_input_in_loop
|
||||
description: LoadInput inside loop body
|
||||
example_rego: "[ input | _ = [1, 2][_] ]"
|
||||
input: {}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2]
|
||||
loop_params:
|
||||
- mode: ForEach
|
||||
collection: 0
|
||||
key_reg: 3
|
||||
value_reg: 4
|
||||
result_reg: 7
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
# Loop body starts
|
||||
- "LoadInput { dest: 5 }"
|
||||
- "ArrayPush { arr: 6, value: 5 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }"
|
||||
# Loop done
|
||||
- "Return { value: 6 }"
|
||||
want_result: [{}, {}]
|
||||
|
||||
- note: load_data_in_arithmetic
|
||||
description: Using LoadData result in arithmetic should fail
|
||||
example_rego: "data + 1"
|
||||
data: {}
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot add Object({}) and Number(1)"
|
||||
|
||||
- note: load_input_in_comparison
|
||||
description: Compare input to literal object
|
||||
example_rego: "input == {}"
|
||||
input: {}
|
||||
literals:
|
||||
- {}
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: load_data_conditional
|
||||
description: LoadData in conditional check
|
||||
example_rego: "data.flag"
|
||||
data: {}
|
||||
literals:
|
||||
- "flag"
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "LoadFalse { dest: 3 }"
|
||||
- "Or { dest: 4, left: 2, right: 3 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined" # flag is undefined
|
||||
|
||||
- note: load_data_chained_index
|
||||
description: Deep path indexing on data
|
||||
example_rego: "data.a.b.c.d"
|
||||
data: {}
|
||||
literals:
|
||||
- "a"
|
||||
- "b"
|
||||
- "c"
|
||||
- "d"
|
||||
instruction_params:
|
||||
chained_index_params:
|
||||
- dest: 5
|
||||
root: 0
|
||||
path_components:
|
||||
- register: 1
|
||||
- register: 2
|
||||
- register: 3
|
||||
- register: 4
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: "#undefined"
|
||||
43
tests/rvm/vm/suites/loop_invalid_iteration.yaml
Normal file
43
tests/rvm/vm/suites/loop_invalid_iteration.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Loop Invalid Iteration Test Suite
|
||||
# Asserts loop error paths and instruction limit behavior.
|
||||
|
||||
cases:
|
||||
- note: loop_start_on_scalar
|
||||
description: LoopStart over scalar treats collection as empty and leaves result false
|
||||
literals:
|
||||
- 1
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 3
|
||||
body_start: 2
|
||||
loop_end: 2
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: false
|
||||
|
||||
- note: instruction_limit_exceeded
|
||||
description: VM stops once max instruction limit reached
|
||||
literals:
|
||||
- [1, 2]
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 3
|
||||
body_start: 2
|
||||
loop_end: 4
|
||||
max_instructions: 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "LoopNext { body_start: 2, loop_end: 4 }"
|
||||
- "Return { value: 3 }"
|
||||
want_error: "exceeded maximum instruction limit"
|
||||
160
tests/rvm/vm/suites/loops/array_comprehensions.yaml
Normal file
160
tests/rvm/vm/suites/loops/array_comprehensions.yaml
Normal file
@@ -0,0 +1,160 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Array Comprehension Test Suite
|
||||
# Tests array comprehensions - collect transformed values based on conditions
|
||||
# Corresponds to Rego's "[transform | condition]" patterns
|
||||
|
||||
cases:
|
||||
- note: array_simple_transform
|
||||
description: Simple array comprehension with transformation
|
||||
example_rego: |
|
||||
# Transform array elements by doubling them
|
||||
[x * 2 | x := [1, 2, 3][_]] # [2, 4, 6]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 2 # multiplier
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 7
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 8
|
||||
comprehension_end: 11
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 9
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "Load { dest: 6, literal_idx: 3 }" # Load multiplier 2 into register 6
|
||||
- "Mul { dest: 9, left: 5, right: 6 }" # Multiply current value by 2, store result in register 9
|
||||
- "ComprehensionYield { value_reg: 9 }" # Add transformed value to comprehension
|
||||
- "LoopNext { body_start: 9, loop_end: 12 }" # Continue to next iteration or exit
|
||||
- "Return { value: 7 }" # Return comprehension collection
|
||||
want_result: [2, 4, 6]
|
||||
|
||||
- note: array_empty_input
|
||||
description: Array comprehension with empty input
|
||||
example_rego: |
|
||||
# Transform empty array
|
||||
[x + 5 | x := [][_]] # [] (empty array)
|
||||
literals:
|
||||
- 5 # addend
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 7
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 2
|
||||
comprehension_end: 6
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 3
|
||||
loop_end: 6
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "Load { dest: 6, literal_idx: 0 }" # Load addend 5 into register 6
|
||||
- "Add { dest: 8, left: 5, right: 6 }" # Add 5 to current value, store result in register 8
|
||||
- "ComprehensionYield { value_reg: 8 }" # Add transformed value to comprehension
|
||||
- "LoopNext { body_start: 3, loop_end: 6 }" # Continue to next iteration or exit
|
||||
- "Return { value: 7 }" # Return comprehension collection
|
||||
want_result: []
|
||||
|
||||
- note: array_single_element
|
||||
description: Array comprehension with single element
|
||||
example_rego: |
|
||||
# Transform single element array
|
||||
[x - 1 | x := [10][_]] # [9]
|
||||
literals:
|
||||
- 10
|
||||
- 1 # subtrahend
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 7
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 4
|
||||
comprehension_end: 8
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [10] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 10 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 10 to array
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "Load { dest: 6, literal_idx: 1 }" # Load subtrahend 1 into register 6
|
||||
- "Sub { dest: 9, left: 5, right: 6 }" # Subtract 1 from current value, store result in register 9
|
||||
- "ComprehensionYield { value_reg: 9 }" # Add transformed value to comprehension
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }" # Continue to next iteration or exit
|
||||
- "Return { value: 7 }" # Return comprehension collection
|
||||
want_result: [9]
|
||||
|
||||
- note: array_with_null_values
|
||||
description: Array comprehension with null value handling
|
||||
example_rego: |
|
||||
# Process array with null values - nulls are preserved
|
||||
[x | x := [1, null, 3][_]] # [1, null, 3]
|
||||
literals:
|
||||
- 1
|
||||
- 3
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 7
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 8
|
||||
comprehension_end: 11
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 9
|
||||
loop_end: 11
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "LoadNull { dest: 2 }" # Load null value
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension and initialize result in register 7
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "ComprehensionYield { value_reg: 5 }" # Add current value (including null) to comprehension
|
||||
- "LoopNext { body_start: 9, loop_end: 11 }" # Continue to next iteration or exit
|
||||
- "Return { value: 7 }" # Return comprehension collection
|
||||
want_result: [1, null, 3]
|
||||
295
tests/rvm/vm/suites/loops/empty.yaml
Normal file
295
tests/rvm/vm/suites/loops/empty.yaml
Normal file
@@ -0,0 +1,295 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Empty Collections Loop Test Suite
|
||||
# Tests behavior of various loop types over empty collections
|
||||
# 1. Comprehensions should evaluate to their empty versions
|
||||
# 2. some..in should evaluate to false
|
||||
# 3. every should evaluate to true
|
||||
|
||||
cases:
|
||||
- note: existential_empty_array
|
||||
description: Existential quantification (some) on empty array should return false
|
||||
example_rego: |
|
||||
# some x in []
|
||||
# x > 0 # false - no elements to satisfy condition
|
||||
literals:
|
||||
- {}
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 2
|
||||
loop_end: 6
|
||||
result_reg: 6
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty array in register 0
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition
|
||||
- "LoopNext { body_start: 2, loop_end: 6 }"
|
||||
- "Return { value: 6 }" # Return false for empty collection
|
||||
want_result: false
|
||||
|
||||
- note: universal_empty_array
|
||||
description: Universal quantification (every) on empty array should return true
|
||||
example_rego: |
|
||||
# every x in []
|
||||
# x > 0 # true - vacuously true (no elements to violate condition)
|
||||
literals:
|
||||
- {}
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 2
|
||||
loop_end: 6
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty array in register 0
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition
|
||||
- "LoopNext { body_start: 2, loop_end: 6 }"
|
||||
- "Return { value: 6 }" # Return true for empty collection (vacuously true)
|
||||
want_result: true
|
||||
|
||||
- note: array_comprehension_empty
|
||||
description: Array comprehension on empty collection should return empty array
|
||||
example_rego: |
|
||||
# [x + 1 | x = []; true] # []
|
||||
# Transform each element by adding 1
|
||||
literals:
|
||||
- {}
|
||||
- 1 # value to add
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 1
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 3
|
||||
comprehension_end: 9
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 4
|
||||
loop_end: 9
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start array comprehension
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
|
||||
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
|
||||
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension
|
||||
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
|
||||
- "AssertCondition { condition: 8 }" # Assert true condition
|
||||
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
|
||||
- "Return { value: 1 }" # Return the result array (should be empty)
|
||||
want_result: []
|
||||
|
||||
- note: set_comprehension_empty
|
||||
description: Set comprehension on empty collection should return empty set
|
||||
example_rego: |
|
||||
# {x + 1 | x = []; true} # set()
|
||||
# Transform each element by adding 1 into a set
|
||||
literals:
|
||||
- {}
|
||||
- 1 # value to add
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 1
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 3
|
||||
comprehension_end: 9
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 4
|
||||
loop_end: 9
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start set comprehension
|
||||
- "LoopStart { params_index: 0 }" # Start loop over array elements
|
||||
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
|
||||
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
|
||||
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension
|
||||
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
|
||||
- "AssertCondition { condition: 8 }" # Assert true condition
|
||||
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
|
||||
- "Return { value: 1 }" # Return the result set (should be empty)
|
||||
want_result:
|
||||
set!: [] # Set serializes as empty array
|
||||
|
||||
- note: object_comprehension_empty
|
||||
description: Object comprehension on empty collection should return empty object
|
||||
example_rego: |
|
||||
# {k: v + 1 | some k, v in {}; true} # {}
|
||||
# Transform each key-value pair
|
||||
literals:
|
||||
- {}
|
||||
- 1 # value to add
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 1
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 3
|
||||
comprehension_end: 9
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 8
|
||||
body_start: 4
|
||||
loop_end: 9
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ComprehensionBegin { params_index: 0 }" # Start object comprehension
|
||||
- "LoopStart { params_index: 0 }" # Start loop over object elements
|
||||
- "Load { dest: 6, literal_idx: 1 }" # Load 1 into register 6
|
||||
- "Add { dest: 7, left: 5, right: 6 }" # Add 1 to current value
|
||||
- "ComprehensionYield { value_reg: 7 }" # Add result to comprehension (object comprehension needs special handling)
|
||||
- "LoadBool { dest: 8, value: true }" # Load true (condition always passes)
|
||||
- "AssertCondition { condition: 8 }" # Assert true condition
|
||||
- "LoopNext { body_start: 4, loop_end: 9 }" # Continue to next iteration or exit
|
||||
- "Return { value: 1 }" # Return the result object (should be empty)
|
||||
want_result: {}
|
||||
|
||||
- note: existential_empty_set
|
||||
description: Existential quantification on empty set should return false
|
||||
example_rego: |
|
||||
# some x in set()
|
||||
# x > 0 # false - no elements in set
|
||||
literals:
|
||||
- {}
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 2
|
||||
loop_end: 6
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }" # Create empty set in register 0
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition
|
||||
- "LoopNext { body_start: 2, loop_end: 6 }"
|
||||
- "Return { value: 6 }" # Return false for empty set
|
||||
want_result: false
|
||||
|
||||
- note: universal_empty_object
|
||||
description: Universal quantification on empty object should return true
|
||||
example_rego: |
|
||||
# every k, v in {}
|
||||
# v > 0 # true - vacuously true (no key-value pairs to violate condition)
|
||||
literals:
|
||||
- {}
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 2
|
||||
loop_end: 6
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 7, literal_idx: 1 }" # Load comparison value 0
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition
|
||||
- "LoopNext { body_start: 2, loop_end: 6 }"
|
||||
- "Return { value: 6 }" # Return true for empty object (vacuously true)
|
||||
want_result: true
|
||||
|
||||
- note: nested_empty_comprehensions
|
||||
description: Nested comprehensions with empty collections
|
||||
example_rego: |
|
||||
# [[y | y = []; true] | x = []; true] # []
|
||||
# Nested array comprehension where both inner and outer collections are empty
|
||||
literals: []
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 0
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
body_start: 3
|
||||
comprehension_end: 16
|
||||
- mode: "Array"
|
||||
collection_reg: 6
|
||||
key_reg: 8
|
||||
value_reg: 9
|
||||
body_start: 7
|
||||
comprehension_end: 12
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
result_reg: 10
|
||||
body_start: 4
|
||||
loop_end: 16
|
||||
- mode: "ForEach"
|
||||
collection: 6
|
||||
key_reg: 8
|
||||
value_reg: 9
|
||||
result_reg: 11
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty outer array in register 0
|
||||
- "ArrayNew { dest: 1 }" # Create empty result array in register 1
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }" # Start outer loop
|
||||
# Inner array comprehension (for each x in outer empty array)
|
||||
- "ArrayNew { dest: 6 }" # Create empty inner array in register 6
|
||||
- "ArrayNew { dest: 7 }" # Create result for inner comprehension in register 7
|
||||
- "ComprehensionBegin { params_index: 1 }"
|
||||
- "LoopStart { params_index: 1 }" # Start inner loop
|
||||
- "ComprehensionYield { value_reg: 9 }" # Push inner value to inner result (never executes)
|
||||
- "LoadBool { dest: 10, value: true }" # Load true
|
||||
- "AssertCondition { condition: 10 }" # Assert true condition for inner loop
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue inner loop
|
||||
- "ComprehensionYield { value_reg: 7 }" # Push inner result to outer result
|
||||
- "LoadBool { dest: 11, value: true }" # Load true
|
||||
- "AssertCondition { condition: 11 }" # Assert true condition for outer loop
|
||||
- "LoopNext { body_start: 4, loop_end: 16 }" # Continue outer loop
|
||||
- "Return { value: 1 }" # Return the nested result (should be empty)
|
||||
want_result: []
|
||||
174
tests/rvm/vm/suites/loops/existential.yaml
Normal file
174
tests/rvm/vm/suites/loops/existential.yaml
Normal file
@@ -0,0 +1,174 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Existential Loops Test Suite (some)
|
||||
# Tests existential quantification loops - succeed if ANY element satisfies the condition
|
||||
# Corresponds to Rego's "some x in collection; condition" patterns
|
||||
|
||||
cases:
|
||||
- note: existential_basic_some
|
||||
description: Basic existential quantification - some element satisfies condition
|
||||
example_rego: |
|
||||
# Check if any element in array is greater than 2
|
||||
some x in [1, 2, 3]
|
||||
x > 2 # true (3 > 2)
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 2 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 2 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 2
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early if condition met
|
||||
- "Return { value: 6 }" # Return result (true if any element satisfied condition)
|
||||
want_result: true
|
||||
|
||||
- note: existential_none_satisfy
|
||||
description: Existential quantification where no element satisfies condition
|
||||
example_rego: |
|
||||
# Check if any element in array is greater than 5
|
||||
some x in [1, 2]
|
||||
x > 5 # false (no element > 5)
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 5 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 6
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 2 }" # Load comparison value 5 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
|
||||
- "LoopNext { body_start: 6, loop_end: 10 }" # Continue to next iteration
|
||||
- "Return { value: 6 }" # Return result (false since no element satisfied condition)
|
||||
want_result: false
|
||||
|
||||
- note: existential_empty_collection
|
||||
description: Existential quantification on empty collection
|
||||
example_rego: |
|
||||
# Check if any element in empty array satisfies condition
|
||||
some x in []
|
||||
x > 0 # false (no elements to check)
|
||||
literals:
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 2
|
||||
loop_end: 6
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 0 }" # Load comparison value 0 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for existential logic
|
||||
- "LoopNext { body_start: 2, loop_end: 6 }" # Continue to next iteration
|
||||
- "Return { value: 6 }" # Return result (false for empty collection)
|
||||
want_result: false
|
||||
|
||||
- note: existential_simplified_arrays
|
||||
description: Existential quantification with simple array test
|
||||
example_rego: |
|
||||
# Check if any element in array is greater than 5
|
||||
# Simplified version: check if [3, 7, 4] contains element > 5
|
||||
some x in [3, 7, 4]
|
||||
x > 5 # true (7 > 5)
|
||||
literals:
|
||||
- 3
|
||||
- 7
|
||||
- 4
|
||||
- 5 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create array [3, 7, 4] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 3
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 3 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 7
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 7 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 4
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 4 to array
|
||||
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 5
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition for existential logic
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration
|
||||
- "Return { value: 6 }" # Return result
|
||||
want_result: true
|
||||
|
||||
- note: some_basic_failure
|
||||
description: Basic existential loop that fails
|
||||
example_rego: "some x in [1, 2, 3]; x > 5" # false because no element > 5
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 5 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create array [1, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "LoopStart { params_index: 0 }" # Start existential loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 5 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 5, store result in register 8
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition (fails for all elements)
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit
|
||||
- "Return { value: 6 }" # Return boolean result from loop
|
||||
want_result: false
|
||||
|
||||
223
tests/rvm/vm/suites/loops/loop_comprehension_interactions.yaml
Normal file
223
tests/rvm/vm/suites/loops/loop_comprehension_interactions.yaml
Normal file
@@ -0,0 +1,223 @@
|
||||
# Advanced Loop and Comprehension Interaction Suite
|
||||
# Exercises nested quantifiers inside comprehensions and object key/value emission.
|
||||
|
||||
cases:
|
||||
- note: array_comprehension_filters_with_inner_any
|
||||
description: Array comprehension keeps only members whose nested array passes an Any loop
|
||||
example_rego: |
|
||||
[arr |
|
||||
arr := [[1, 0], [1, 2]][_];
|
||||
some v in arr; v == 0
|
||||
]
|
||||
literals:
|
||||
- 1
|
||||
- 0
|
||||
- 2
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 7
|
||||
result_reg: 7
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 14
|
||||
comprehension_end: 23
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 15
|
||||
loop_end: 23
|
||||
- mode: "Any"
|
||||
collection: 11
|
||||
key_reg: 13
|
||||
value_reg: 14
|
||||
result_reg: 15
|
||||
body_start: 16
|
||||
loop_end: 21
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "ArrayNew { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 1, value: 3 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "ArrayNew { dest: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 4, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 4, value: 6 }"
|
||||
- "ArrayPush { arr: 0, value: 4 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
- "Load { dest: 16, literal_idx: 1 }"
|
||||
- "Eq { dest: 17, left: 14, right: 16 }"
|
||||
- "AssertCondition { condition: 17 }"
|
||||
- "ComprehensionYield { value_reg: 11 }"
|
||||
- "LoopNext { body_start: 16, loop_end: 21 }"
|
||||
- "LoopNext { body_start: 15, loop_end: 23 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Return { value: 7 }"
|
||||
want_result:
|
||||
- [1, 0]
|
||||
|
||||
- note: array_comprehension_requires_inner_every
|
||||
description: Array comprehension keeps only members whose nested array passes an Every loop
|
||||
example_rego: |
|
||||
[arr |
|
||||
arr := [[1, 1], [-1, 2], [2, 3]][_];
|
||||
every v in arr; v > 0
|
||||
]
|
||||
literals:
|
||||
- 1
|
||||
- -1
|
||||
- 2
|
||||
- 3
|
||||
- 0
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 9
|
||||
result_reg: 9
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 20
|
||||
comprehension_end: 29
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 20
|
||||
loop_end: 29
|
||||
- mode: "ForEach"
|
||||
collection: 11
|
||||
key_reg: 13
|
||||
value_reg: 14
|
||||
result_reg: 18
|
||||
body_start: 22
|
||||
loop_end: 26
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "ArrayNew { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 1, value: 2 }"
|
||||
- "ArrayPush { arr: 1, value: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "ArrayNew { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 3, value: 5 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 6, value: 7 }"
|
||||
- "Load { dest: 8, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 6, value: 8 }"
|
||||
- "ArrayPush { arr: 0, value: 6 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "LoadTrue { dest: 15 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
- "Load { dest: 16, literal_idx: 4 }"
|
||||
- "Gt { dest: 17, left: 14, right: 16 }"
|
||||
- "And { dest: 15, left: 15, right: 17 }"
|
||||
- "LoopNext { body_start: 22, loop_end: 26 }"
|
||||
- "AssertCondition { condition: 15 }"
|
||||
- "ComprehensionYield { value_reg: 11 }"
|
||||
- "LoopNext { body_start: 20, loop_end: 29 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Return { value: 9 }"
|
||||
want_result:
|
||||
- [1, 1]
|
||||
- [2, 3]
|
||||
|
||||
- note: object_comprehension_with_nested_any
|
||||
description: Object comprehension emits keys only when nested Any loop succeeds
|
||||
example_rego: |
|
||||
{ entry[0]: true |
|
||||
entry := [["a", [1, 2]], ["b", [1]]][_];
|
||||
some v in entry[1]; v % 2 == 0
|
||||
}
|
||||
literals:
|
||||
- {}
|
||||
- "a"
|
||||
- 1
|
||||
- 2
|
||||
- "b"
|
||||
- 0
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 12
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 12
|
||||
result_reg: 12
|
||||
key_reg: 17
|
||||
value_reg: 22
|
||||
body_start: 21
|
||||
comprehension_end: 36
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 13
|
||||
value_reg: 14
|
||||
result_reg: 15
|
||||
body_start: 22
|
||||
loop_end: 36
|
||||
- mode: "Any"
|
||||
collection: 19
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 27
|
||||
loop_end: 33
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "ArrayNew { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 1, value: 2 }"
|
||||
- "ArrayNew { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 3, value: 5 }"
|
||||
- "ArrayPush { arr: 1, value: 3 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 4 }"
|
||||
- "ArrayPush { arr: 6, value: 7 }"
|
||||
- "ArrayNew { dest: 8 }"
|
||||
- "Load { dest: 9, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 8, value: 9 }"
|
||||
- "ArrayPush { arr: 6, value: 8 }"
|
||||
- "ArrayPush { arr: 0, value: 6 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 16, literal_idx: 5 }"
|
||||
- "Index { dest: 17, container: 14, key: 16 }"
|
||||
- "Load { dest: 18, literal_idx: 2 }"
|
||||
- "Index { dest: 19, container: 14, key: 18 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
- "Load { dest: 23, literal_idx: 3 }"
|
||||
- "Mod { dest: 24, left: 21, right: 23 }"
|
||||
- "Load { dest: 25, literal_idx: 5 }"
|
||||
- "Eq { dest: 26, left: 24, right: 25 }"
|
||||
- "AssertCondition { condition: 26 }"
|
||||
- "LoopNext { body_start: 27, loop_end: 33 }"
|
||||
- "AssertCondition { condition: 22 }"
|
||||
- "ComprehensionYield { value_reg: 22, key_reg: 17 }"
|
||||
- "LoopNext { body_start: 22, loop_end: 36 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Return { value: 12 }"
|
||||
want_result: {"a": true}
|
||||
183
tests/rvm/vm/suites/loops/nested.yaml
Normal file
183
tests/rvm/vm/suites/loops/nested.yaml
Normal file
@@ -0,0 +1,183 @@
|
||||
name: "Nested Loops Test Suite"
|
||||
description: "Test various combinations and levels of nesting for different looping constructs"
|
||||
|
||||
cases:
|
||||
- note: "simple_nested_comprehension"
|
||||
description: "Test simple nested array comprehension"
|
||||
example_rego: |
|
||||
[[x | x := [1, 2][_]] | _ := [1, 2][_]]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 1
|
||||
key_reg: 2
|
||||
value_reg: 3
|
||||
body_start: 6
|
||||
comprehension_end: 23
|
||||
- mode: "Array"
|
||||
collection_reg: 11
|
||||
key_reg: 12
|
||||
value_reg: 13
|
||||
body_start: 12
|
||||
comprehension_end: 20
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 6
|
||||
key_reg: 7
|
||||
value_reg: 8
|
||||
result_reg: 9
|
||||
body_start: 7
|
||||
loop_end: 22
|
||||
- mode: "ForEach"
|
||||
collection: 16
|
||||
key_reg: 17
|
||||
value_reg: 18
|
||||
result_reg: 19
|
||||
body_start: 15
|
||||
loop_end: 18
|
||||
instructions:
|
||||
- "ComprehensionBegin { params_index: 0 }" # array comprehension in r1, body: 4-21 (P0)
|
||||
- "Load { dest: 4, literal_idx: 0 }" # Load literal: 1
|
||||
- "Load { dest: 5, literal_idx: 1 }" # Load literal: 2
|
||||
- "ArrayNew { dest: 6 }" # Create empty array r6
|
||||
- "ArrayPush { arr: 6, value: 4 }" # Push r4 to r6
|
||||
- "ArrayPush { arr: 6, value: 5 }" # Push r5 to r6 -> [1, 2]
|
||||
- "LoopStart { params_index: 0 }" # foreach loop over r6, body: 8-20 (P0)
|
||||
# Outer loop body starts here (index 7)
|
||||
- "Move { dest: 10, src: 8 }" # Copy value from r8 to r10
|
||||
- "ComprehensionBegin { params_index: 1 }" # array comprehension in r11, body: 10-18 (P1)
|
||||
- "Load { dest: 14, literal_idx: 0 }" # Load literal: 1
|
||||
- "Load { dest: 15, literal_idx: 1 }" # Load literal: 2
|
||||
- "ArrayNew { dest: 16 }" # Create empty array r16
|
||||
- "ArrayPush { arr: 16, value: 14 }" # Push r14 to r16
|
||||
- "ArrayPush { arr: 16, value: 15 }" # Push r15 to r16 -> [1, 2]
|
||||
- "LoopStart { params_index: 1 }" # foreach loop over r16, body: 14-17 (P1)
|
||||
# Inner loop body starts here (index 16)
|
||||
- "Move { dest: 20, src: 18 }" # Copy value from r18 to r20
|
||||
- "ComprehensionYield { value_reg: 20 }" # Yield value to comprehension
|
||||
- "LoopNext { body_start: 16, loop_end: 19 }" # continue → 16 or exit → 19
|
||||
# Inner comprehension end (index 19)
|
||||
- "ComprehensionEnd" # End comprehension block
|
||||
- "ComprehensionYield { value_reg: 11 }" # Yield value to comprehension
|
||||
- "LoopNext { body_start: 7, loop_end: 22 }" # continue → 7 or exit → 22
|
||||
# Outer comprehension end (index 22)
|
||||
- "ComprehensionEnd" # End comprehension block
|
||||
- "Move { dest: 0, src: 1 }" # Copy value from r1 to r0
|
||||
- "Return { value: 0 }" # Return value from r0
|
||||
want_result: [[1, 2], [1, 2]]
|
||||
|
||||
- note: "some_nested_comprehension"
|
||||
description: "Test some with nested array comprehension"
|
||||
example_rego: |
|
||||
[1, 2][_] == [x | x := [1, 2, 3][_]; x > 1][_]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 12
|
||||
result_reg: 12
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 16
|
||||
comprehension_end: 20
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 7
|
||||
value_reg: 8
|
||||
result_reg: 9
|
||||
body_start: 14
|
||||
loop_end: 27
|
||||
- mode: "ForEach"
|
||||
collection: 3
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 21
|
||||
body_start: 16
|
||||
loop_end: 20
|
||||
- mode: "Any"
|
||||
collection: 12
|
||||
key_reg: 13
|
||||
value_reg: 14
|
||||
result_reg: 15
|
||||
body_start: 22
|
||||
loop_end: 25
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Build left array [1, 2]
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Index 1: Load literal 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Index 2
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Index 3: Load literal 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Index 4
|
||||
- "ArrayNew { dest: 3 }" # Index 5: Build source array [1, 2, 3]
|
||||
- "Load { dest: 4, literal_idx: 0 }" # Index 6
|
||||
- "ArrayPush { arr: 3, value: 4 }" # Index 7
|
||||
- "Load { dest: 5, literal_idx: 1 }" # Index 8
|
||||
- "ArrayPush { arr: 3, value: 5 }" # Index 9
|
||||
- "Load { dest: 6, literal_idx: 2 }" # Index 10
|
||||
- "ArrayPush { arr: 3, value: 6 }" # Index 11
|
||||
- "Load { dest: 16, literal_idx: 0 }" # Index 12: Load 1 for comparison (stays stable)
|
||||
- "LoopStart { params_index: 0 }" # Index 13: Start outer some loop over [1, 2]
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 14: Build filtered comprehension
|
||||
- "LoopStart { params_index: 1 }" # Index 15: Iterate source values for comprehension
|
||||
- "Gt { dest: 17, left: 11, right: 16 }" # Index 16: Check x > 1
|
||||
- "AssertCondition { condition: 17 }" # Index 17: Skip values <= 1
|
||||
- "ComprehensionYield { value_reg: 11 }" # Index 18: Include qualifying value
|
||||
- "LoopNext { body_start: 16, loop_end: 20 }" # Index 19: Continue comprehension loop
|
||||
- "ComprehensionEnd" # Index 20: Finish comprehension block
|
||||
- "LoopStart { params_index: 2 }" # Index 21: Iterate comprehension results
|
||||
- "Eq { dest: 18, left: 8, right: 14 }" # Index 22: Compare outer value with result value
|
||||
- "AssertCondition { condition: 18 }" # Index 23: Success when values match
|
||||
- "LoopNext { body_start: 22, loop_end: 25 }" # Index 24: Continue inner any loop
|
||||
- "AssertCondition { condition: 15 }" # Index 25: Require some match from inner any loop
|
||||
- "LoopNext { body_start: 14, loop_end: 27 }" # Index 26: Continue outer some loop
|
||||
- "Return { value: 9 }" # Index 27
|
||||
want_result: true
|
||||
|
||||
- note: "nested_with_condition"
|
||||
description: "Test nested loops with conditional logic"
|
||||
example_rego: |
|
||||
[x | x := [1, 2, 3][_]; x > 1]
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 6
|
||||
result_reg: 6
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 9
|
||||
comprehension_end: 14
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 9
|
||||
loop_end: 14
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Create input array [1, 2, 3]
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Index 1: Load 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Index 3: Load 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Index 4: Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Index 5: Load 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Index 6: Push 3 to array
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 7: Start comprehension
|
||||
- "LoopStart { params_index: 0 }" # Index 8: Start loop
|
||||
- "Load { dest: 7, literal_idx: 0 }" # Index 9: Load 1 for comparison
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Index 10: x > 1
|
||||
- "AssertCondition { condition: 8 }" # Index 11: Assert x > 1 (skip if false)
|
||||
- "ComprehensionYield { value_reg: 5 }" # Index 12: Push x to result if condition true
|
||||
- "LoopNext { body_start: 9, loop_end: 13 }" # Index 13: Continue loop
|
||||
- "Return { value: 6 }" # Index 14: Return comprehension result
|
||||
want_result: [2, 3]
|
||||
7
tests/rvm/vm/suites/loops/nested_fixed.yaml
Normal file
7
tests/rvm/vm/suites/loops/nested_fixed.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Placeholder for nested fixed loops - currently empty
|
||||
# TODO: Add test cases for fixed nested loop scenarios
|
||||
|
||||
cases: []
|
||||
192
tests/rvm/vm/suites/loops/object_comprehensions.yaml
Normal file
192
tests/rvm/vm/suites/loops/object_comprehensions.yaml
Normal file
@@ -0,0 +1,192 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Object Comprehension Test Suite
|
||||
# Tests object comprehensions - construct objects with key-value pairs based on conditions
|
||||
# Corresponds to Rego's "{key: value | condition}" patterns
|
||||
|
||||
cases:
|
||||
- note: object_simple_key_value
|
||||
description: Simple object comprehension with computed values
|
||||
example_rego: |
|
||||
# Create object mapping each value to its double
|
||||
{x: x * 2 | x := [1, 2, 3][_]} # {1: 2, 2: 4, 3: 6}
|
||||
literals:
|
||||
- {}
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 2 # multiplier
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 9
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 9
|
||||
result_reg: 9
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 10
|
||||
comprehension_end: 13
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 10
|
||||
body_start: 10
|
||||
loop_end: 14
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Create input array [1, 2, 3]
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Index 2
|
||||
- "Load { dest: 2, literal_idx: 2 }" # Index 3: Load 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Index 4
|
||||
- "Load { dest: 3, literal_idx: 3 }" # Index 5: Load 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Index 6
|
||||
- "ObjectCreate { params_index: 0 }" # Index 7: Create empty result object in r9
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 8: Start object comprehension
|
||||
- "LoopStart { params_index: 0 }" # Index 9: Start loop
|
||||
- "Load { dest: 11, literal_idx: 4 }" # Index 10: Load multiplier 2
|
||||
- "Mul { dest: 12, left: 5, right: 11 }" # Index 11: Multiply value by 2
|
||||
- "ComprehensionYield { value_reg: 12, key_reg: 5 }" # Index 12: Add key-value pair
|
||||
- "LoopNext { body_start: 10, loop_end: 14 }" # Index 13: Continue loop
|
||||
- "Return { value: 9 }" # Index 14: Return result object
|
||||
want_result: {1: 2, 2: 4, 3: 6}
|
||||
|
||||
- note: object_empty_input
|
||||
description: Object comprehension with empty input
|
||||
example_rego: |
|
||||
# Create object from empty array
|
||||
{x: x + 5 | x := [][_]} # {} (empty object)
|
||||
literals:
|
||||
- {}
|
||||
- 5 # addend
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 9
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 9
|
||||
result_reg: 9
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 4
|
||||
comprehension_end: 7
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 10
|
||||
body_start: 4
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Create empty input array
|
||||
- "ObjectCreate { params_index: 0 }" # Index 1: Create empty result object in r9
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 2: Start object comprehension
|
||||
- "LoopStart { params_index: 0 }" # Index 3: Start loop
|
||||
- "Load { dest: 10, literal_idx: 1 }" # Index 4: Load addend 5
|
||||
- "Add { dest: 11, left: 5, right: 10 }" # Index 5: Add 5 to value
|
||||
- "ComprehensionYield { value_reg: 11, key_reg: 5 }" # Index 6: Add key-value pair
|
||||
- "LoopNext { body_start: 4, loop_end: 8 }" # Index 7: Continue loop
|
||||
- "Return { value: 9 }" # Index 8: Return result object
|
||||
want_result: {}
|
||||
|
||||
- note: object_single_element
|
||||
description: Object comprehension with single element
|
||||
example_rego: |
|
||||
# Create object with single key-value pair
|
||||
{x: x - 1 | x := [5][_]} # {5: 4}
|
||||
literals:
|
||||
- {}
|
||||
- 5
|
||||
- 1 # subtrahend
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 9
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 9
|
||||
result_reg: 9
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 6
|
||||
comprehension_end: 9
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 10
|
||||
body_start: 6
|
||||
loop_end: 10
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Create input array [5]
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 5
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 5 to array
|
||||
- "ObjectCreate { params_index: 0 }" # Index 3: Create empty result object in r9
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 4: Start object comprehension
|
||||
- "LoopStart { params_index: 0 }" # Index 5: Start loop
|
||||
- "Load { dest: 10, literal_idx: 2 }" # Index 6: Load subtrahend 1
|
||||
- "Sub { dest: 11, left: 5, right: 10 }" # Index 7: Subtract 1 from value
|
||||
- "ComprehensionYield { value_reg: 11, key_reg: 5 }" # Index 8: Add key-value pair
|
||||
- "LoopNext { body_start: 6, loop_end: 10 }" # Index 9: Continue loop
|
||||
- "Return { value: 9 }" # Index 10: Return result object
|
||||
want_result: {5: 4}
|
||||
|
||||
- note: object_with_null_keys
|
||||
description: Object comprehension with null keys and values
|
||||
example_rego: |
|
||||
# Create object with null keys/values
|
||||
{x: x | x := [1, null, 2][_]} # {1: 1, null: null, 2: 2}
|
||||
literals:
|
||||
- {}
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 9
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
comprehension_begin_params:
|
||||
- mode: "Object"
|
||||
collection_reg: 9
|
||||
result_reg: 9
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
body_start: 10
|
||||
comprehension_end: 11
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 10
|
||||
body_start: 10
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Index 0: Create input array
|
||||
- "Load { dest: 1, literal_idx: 1 }" # Index 1: Load 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Index 2: Push 1 to array
|
||||
- "LoadNull { dest: 2 }" # Index 3: Load null value
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Index 4: Push null to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Index 5: Load 2
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Index 6: Push 2 to array
|
||||
- "ObjectCreate { params_index: 0 }" # Index 7: Create empty result object in r9
|
||||
- "ComprehensionBegin { params_index: 0 }" # Index 8: Start object comprehension
|
||||
- "LoopStart { params_index: 0 }" # Index 9: Start loop
|
||||
- "ComprehensionYield { value_reg: 5, key_reg: 5 }" # Index 10: Use value as both key and value
|
||||
- "LoopNext { body_start: 10, loop_end: 12 }" # Index 11: Continue loop
|
||||
- "Return { value: 9 }" # Index 12: Return result object
|
||||
want_result: {1: 1, null: null, 2: 2}
|
||||
139
tests/rvm/vm/suites/loops/set_comprehensions.yaml
Normal file
139
tests/rvm/vm/suites/loops/set_comprehensions.yaml
Normal file
@@ -0,0 +1,139 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Set Comprehension Test Suite
|
||||
# Tests set comprehensions - collect unique transformed values based on conditions
|
||||
# Corresponds to Rego's "{transform | condition}" patterns
|
||||
|
||||
cases:
|
||||
- note: set_simple_transform
|
||||
description: Simple set comprehension with transformation
|
||||
example_rego: |
|
||||
# Transform array values into set - duplicates removed
|
||||
{x * 2 | x := [1, 2, 2, 3][_]} # {2, 4, 6} (duplicates removed)
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 2 # multiplier
|
||||
instruction_params:
|
||||
comprehension_start_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 10
|
||||
comprehension_end: 14
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 again to array (duplicate)
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "SetNew { dest: 7 }" # Initialize result set in register 7
|
||||
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
|
||||
- "Load { dest: 6, literal_idx: 3 }" # Load multiplier 2 into register 6
|
||||
- "Mul { dest: 10, left: 5, right: 6 }" # Multiply current value by 2, store result in register 10
|
||||
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set (auto-deduplicates)
|
||||
- "Halt" # End comprehension
|
||||
- "Return { value: 7 }" # Return result set
|
||||
want_result:
|
||||
set!: [2, 4, 6]
|
||||
|
||||
- note: set_empty_input
|
||||
description: Set comprehension with empty input
|
||||
example_rego: |
|
||||
# Transform empty array into set
|
||||
{x + 5 | x := [][_]} # {} (empty set)
|
||||
literals:
|
||||
- 5 # addend
|
||||
instruction_params:
|
||||
comprehension_start_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 3
|
||||
comprehension_end: 7
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "SetNew { dest: 7 }" # Initialize result set in register 7
|
||||
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
|
||||
- "Load { dest: 6, literal_idx: 0 }" # Load addend 5 into register 6
|
||||
- "Add { dest: 10, left: 5, right: 6 }" # Add 5 to current value, store result in register 10
|
||||
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set
|
||||
- "Halt" # End comprehension
|
||||
- "Return { value: 7 }" # Return result set
|
||||
want_result:
|
||||
set!: []
|
||||
|
||||
- note: set_single_element
|
||||
description: Set comprehension with single element
|
||||
example_rego: |
|
||||
# Transform single element into set
|
||||
{x - 1 | x := [10][_]} # {9}
|
||||
literals:
|
||||
- 10
|
||||
- 1 # subtrahend
|
||||
instruction_params:
|
||||
comprehension_start_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 5
|
||||
comprehension_end: 9
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [10] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 10 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 10 to array
|
||||
- "SetNew { dest: 7 }" # Initialize result set in register 7
|
||||
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
|
||||
- "Load { dest: 6, literal_idx: 1 }" # Load subtrahend 1 into register 6
|
||||
- "Sub { dest: 10, left: 5, right: 6 }" # Subtract 1 from current value, store result in register 10
|
||||
- "ComprehensionAdd { value_reg: 10 }" # Add transformed value to result set
|
||||
- "Halt" # End comprehension
|
||||
- "Return { value: 7 }" # Return result set
|
||||
want_result:
|
||||
set!: [9]
|
||||
|
||||
- note: set_with_null_deduplication
|
||||
description: Set comprehension with null values and deduplication
|
||||
example_rego: |
|
||||
# Collect unique values including nulls
|
||||
{x | x := [1, null, 1, null, 2][_]} # {1, null, 2}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
comprehension_start_params:
|
||||
- mode: "Set"
|
||||
collection_reg: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 7
|
||||
body_start: 11
|
||||
comprehension_end: 13
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "LoadNull { dest: 2 }" # Load null value
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 again (duplicate)
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push null again (duplicate)
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load 2
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 2 to array
|
||||
- "SetNew { dest: 7 }" # Initialize result set in register 7
|
||||
- "ComprehensionStart { params_index: 0 }" # Start set comprehension
|
||||
- "ComprehensionAdd { value_reg: 5 }" # Add current value to result set (auto-deduplicates)
|
||||
- "Halt" # End comprehension
|
||||
- "Return { value: 7 }" # Return result set
|
||||
want_result:
|
||||
set!: [1, null, 2]
|
||||
143
tests/rvm/vm/suites/loops/universal.yaml
Normal file
143
tests/rvm/vm/suites/loops/universal.yaml
Normal file
@@ -0,0 +1,143 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Universal Loops Test Suite
|
||||
# Tests universal quantification - succeed if ALL elements satisfy the condition
|
||||
# Corresponds to Rego's "every x in collection; condition" patterns
|
||||
|
||||
cases:
|
||||
- note: universal_basic_every
|
||||
description: Basic universal quantification - every element satisfies condition
|
||||
example_rego: |
|
||||
# Check if every element in array is greater than 0
|
||||
every x in [1, 2, 3] {
|
||||
x > 0 # true (all elements > 0)
|
||||
}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "LoopStart { params_index: 0 }" # Start universal loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 0 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early if condition fails
|
||||
- "Return { value: 6 }" # Return result (true if all elements satisfied condition)
|
||||
want_result: true
|
||||
|
||||
- note: universal_one_fails
|
||||
description: Universal quantification where one element fails condition
|
||||
example_rego: |
|
||||
# Check if every element in array is greater than 1
|
||||
every x in [1, 2, 3] {
|
||||
x > 1 # false (1 is not > 1)
|
||||
}
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 1 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array [1, 2, 3] in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 1 into register 1
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 1 to array
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load 2 into register 2
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push 2 to array
|
||||
- "Load { dest: 3, literal_idx: 2 }" # Load 3 into register 3
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 3 to array
|
||||
- "LoopStart { params_index: 0 }" # Start universal loop using parameter table index 0
|
||||
- "Load { dest: 7, literal_idx: 3 }" # Load comparison value 1 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 1
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration or exit early on failure
|
||||
- "Return { value: 6 }" # Return result (false since first element failed condition)
|
||||
want_result: false
|
||||
|
||||
- note: universal_empty_collection
|
||||
description: Universal quantification on empty collection
|
||||
example_rego: |
|
||||
# Check if every element in empty array satisfies condition
|
||||
every x in [] {
|
||||
x > 0 # true (vacuously true - all 0 elements satisfy condition)
|
||||
}
|
||||
literals:
|
||||
- 0 # comparison value
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 2
|
||||
loop_end: 5
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create empty input array in register 0
|
||||
- "LoopStart { params_index: 0 }" # Start universal loop
|
||||
- "Load { dest: 7, literal_idx: 0 }" # Load comparison value 0 into register 7
|
||||
- "Gt { dest: 8, left: 5, right: 7 }" # Check if current value > 0
|
||||
- "LoopNext { body_start: 2, loop_end: 5 }" # Continue to next iteration
|
||||
- "Return { value: 6 }" # Return result (true for empty collection - vacuous truth)
|
||||
want_result: true
|
||||
|
||||
- note: universal_null_handling
|
||||
description: Universal quantification with null values
|
||||
example_rego: |
|
||||
# Test behavior with null values - should handle gracefully
|
||||
every x in [2, null, 4] {
|
||||
x != null # false (null fails the condition)
|
||||
}
|
||||
literals:
|
||||
- 2
|
||||
- 4
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Every"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 8
|
||||
loop_end: 12
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }" # Create input array in register 0
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load 2
|
||||
- "ArrayPush { arr: 0, value: 1 }" # Push 2 to array
|
||||
- "LoadNull { dest: 2 }" # Load null value
|
||||
- "ArrayPush { arr: 0, value: 2 }" # Push null to array
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load 4
|
||||
- "ArrayPush { arr: 0, value: 3 }" # Push 4 to array
|
||||
- "LoopStart { params_index: 0 }" # Start universal loop
|
||||
- "LoadNull { dest: 7 }" # Load null for comparison
|
||||
- "Ne { dest: 8, left: 5, right: 7 }" # Check if current value != null
|
||||
- "AssertCondition { condition: 8 }" # Assert the condition result for universal logic
|
||||
- "LoopNext { body_start: 8, loop_end: 12 }" # Continue to next iteration
|
||||
- "Return { value: 6 }" # Return result
|
||||
want_result: false
|
||||
376
tests/rvm/vm/suites/null_undefined_handling.yaml
Normal file
376
tests/rvm/vm/suites/null_undefined_handling.yaml
Normal file
@@ -0,0 +1,376 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Null and Undefined Handling Test Suite
|
||||
# Tests null and undefined behavior across all instruction families
|
||||
# Covers arithmetic, comparisons, logical ops, indexing, loops, and comprehensions
|
||||
|
||||
cases:
|
||||
- note: load_null_basic
|
||||
description: LoadNull loads null value
|
||||
example_rego: "null"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: null_in_arithmetic_add
|
||||
description: Adding null is a type error
|
||||
example_rego: "null + 1"
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot add Null"
|
||||
|
||||
- note: null_in_arithmetic_sub
|
||||
description: Subtracting null is a type error
|
||||
example_rego: "5 - null"
|
||||
literals:
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Sub { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot subtract Number(5)"
|
||||
|
||||
- note: null_in_arithmetic_mul
|
||||
description: Multiplying null is a type error
|
||||
example_rego: "null * 3"
|
||||
literals:
|
||||
- 3
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Mul { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot multiply Null"
|
||||
|
||||
- note: null_in_comparison_eq
|
||||
description: null equals null
|
||||
example_rego: "null == null"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: null_not_equal_to_number
|
||||
description: null is not equal to numbers
|
||||
example_rego: "null == 0"
|
||||
literals:
|
||||
- 0
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
- note: null_not_equal_to_false
|
||||
description: null is not equal to false
|
||||
example_rego: "null == false"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "LoadFalse { dest: 1 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
- note: null_in_comparison_lt
|
||||
description: Ordering comparison treats null as less than numbers
|
||||
example_rego: "null < 5"
|
||||
literals:
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Lt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: null_in_logical_and
|
||||
description: Logical AND treats null as truthy
|
||||
example_rego: "null && true"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "And { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: null_in_logical_or
|
||||
description: Logical OR treats null as truthy
|
||||
example_rego: "null || false"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "LoadFalse { dest: 1 }"
|
||||
- "Or { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: null_in_logical_not
|
||||
description: Logical NOT treats null as truthy (returns false)
|
||||
example_rego: "not null"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Not { dest: 1, operand: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: false
|
||||
|
||||
- note: null_as_array_index
|
||||
description: Indexing array with null key
|
||||
example_rego: "[1, 2, 3][null]"
|
||||
literals:
|
||||
- [1, 2, 3]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined" # null is not a valid array index
|
||||
|
||||
- note: null_as_object_key
|
||||
description: Indexing object with null key
|
||||
example_rego: "{\"a\": 1, \"b\": 2}[null]"
|
||||
literals:
|
||||
- {"a": 1, "b": 2}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined" # null key doesn't exist
|
||||
|
||||
- note: null_in_contains_check
|
||||
description: Contains check with null
|
||||
example_rego: "null in [1, 2, null, 3]"
|
||||
literals:
|
||||
- [1, 2, null, 3]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Contains { dest: 2, collection: 0, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: true
|
||||
|
||||
- note: null_in_set
|
||||
description: null can be a set member
|
||||
example_rego: "{1, null, 3}"
|
||||
literals:
|
||||
- 1
|
||||
- 3
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "LoadNull { dest: 2 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- null
|
||||
- 3
|
||||
|
||||
- note: null_in_array
|
||||
description: null can be an array element
|
||||
example_rego: "[1, null, 3]"
|
||||
literals:
|
||||
- 1
|
||||
- 3
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "LoadNull { dest: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [1, null, 3]
|
||||
|
||||
- note: null_in_object_value
|
||||
description: null can be an object value
|
||||
example_rego: "{\"a\": 1, \"b\": null}"
|
||||
literals:
|
||||
- "a"
|
||||
- 1
|
||||
- "b"
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 3
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [1, 2]
|
||||
- [3, 4]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "LoadNull { dest: 4 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"a": 1, "b": null}
|
||||
|
||||
- note: undefined_in_arithmetic
|
||||
description: Arithmetic with undefined register produces undefined
|
||||
example_rego: "undefined_var + 1"
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
# r0 is undefined (not loaded)
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_comparison
|
||||
description: Comparison with undefined returns undefined
|
||||
example_rego: "undefined_var == 5"
|
||||
literals:
|
||||
- 5
|
||||
instructions:
|
||||
# r0 is undefined
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Eq { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_logical_ops
|
||||
description: Logical operations with undefined return undefined
|
||||
example_rego: "undefined_var && true"
|
||||
literals: []
|
||||
instructions:
|
||||
# r0 is undefined
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "And { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_indexing
|
||||
description: Indexing undefined returns undefined
|
||||
example_rego: "undefined_var[\"key\"]"
|
||||
literals:
|
||||
- "key"
|
||||
instructions:
|
||||
# r0 is undefined
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_array_create
|
||||
description: ArrayCreate with undefined element returns undefined
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3] # r3 is undefined
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
# r3 is undefined
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_object_create_key
|
||||
description: ObjectCreate with undefined key returns undefined
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 2
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [1, 2]
|
||||
- [3, 4]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
# r3, r4 are undefined
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_object_create_value
|
||||
description: ObjectCreate with undefined value returns undefined
|
||||
literals:
|
||||
- "key1"
|
||||
- "key2"
|
||||
- 1
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 3
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [1, 3]
|
||||
- [2, 4] # r4 is undefined
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
# r4 is undefined
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: undefined_in_loop_collection
|
||||
description: Loop over undefined collection fails gracefully
|
||||
literals: []
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0 # r0 is undefined
|
||||
key_reg: 1
|
||||
value_reg: 2
|
||||
result_reg: 4
|
||||
body_start: 1
|
||||
loop_end: 3
|
||||
instructions:
|
||||
# r0 is undefined
|
||||
- "LoopStart { params_index: 0 }"
|
||||
# Loop body (never executed because collection is undefined)
|
||||
- "LoadTrue { dest: 3 }"
|
||||
- "LoopNext { body_start: 1, loop_end: 3 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: "#undefined" # Loop over undefined collection yields undefined
|
||||
|
||||
- note: undefined_propagation_through_chain
|
||||
description: Undefined propagates through operation chain
|
||||
example_rego: "(undefined_var + 1) * 2"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instructions:
|
||||
# r0 is undefined
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }" # r2 becomes undefined
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "Mul { dest: 4, left: 2, right: 3 }" # r4 becomes undefined
|
||||
- "Return { value: 4 }"
|
||||
want_result: "#undefined"
|
||||
276
tests/rvm/vm/suites/object_operations.yaml
Normal file
276
tests/rvm/vm/suites/object_operations.yaml
Normal file
@@ -0,0 +1,276 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Object Operations Test Suite
|
||||
# Tests advanced object creation, manipulation, and edge cases
|
||||
# Covers dynamic keys, collisions, non-string keys, and template validation
|
||||
|
||||
cases:
|
||||
- note: object_key_collision_overwrite
|
||||
description: Setting same key twice should overwrite the value
|
||||
example_rego: "{\"key\": 1, \"key\": 2}"
|
||||
literals:
|
||||
- {}
|
||||
- "key"
|
||||
- 1
|
||||
- 2
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # key
|
||||
- "Load { dest: 2, literal_idx: 2 }" # value 1
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # value 2
|
||||
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Overwrite
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"key": 2}
|
||||
|
||||
- note: object_dynamic_key_generation
|
||||
description: Generate object keys dynamically from loop iteration
|
||||
example_rego: "{sprintf(\"key_%d\", [i]): i | i := [1, 2][_]}"
|
||||
literals:
|
||||
- {}
|
||||
- 1
|
||||
- 2
|
||||
- "key_1"
|
||||
- "key_2"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 5
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 7
|
||||
loop_end: 13
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ArrayNew { dest: 5 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 5, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 5, value: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
# Generate dynamic key based on value
|
||||
- "Load { dest: 20, literal_idx: 3 }" # "key_1" when i=1
|
||||
- "Load { dest: 21, literal_idx: 4 }" # "key_2" when i=2
|
||||
# For simplicity, we'll set both keys
|
||||
- "ObjectSet { obj: 0, key: 20, value: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ObjectSet { obj: 0, key: 21, value: 3 }"
|
||||
- "LoopNext { body_start: 7, loop_end: 13 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"key_1": 1, "key_2": 2}
|
||||
|
||||
- note: object_number_keys
|
||||
description: Object with number keys
|
||||
example_rego: "{1: \"one\", 2: \"two\", 42: \"answer\"}"
|
||||
literals:
|
||||
- {}
|
||||
- 1
|
||||
- "one"
|
||||
- 2
|
||||
- "two"
|
||||
- 42
|
||||
- "answer"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # key 1
|
||||
- "Load { dest: 2, literal_idx: 2 }" # value "one"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # key 2
|
||||
- "Load { dest: 4, literal_idx: 4 }" # value "two"
|
||||
- "ObjectSet { obj: 0, key: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 5 }" # key 42
|
||||
- "Load { dest: 6, literal_idx: 6 }" # value "answer"
|
||||
- "ObjectSet { obj: 0, key: 5, value: 6 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {1: "one", 2: "two", 42: "answer"}
|
||||
|
||||
- note: object_boolean_keys
|
||||
description: Object with boolean keys
|
||||
example_rego: "{true: \"yes\", false: \"no\"}"
|
||||
literals:
|
||||
- {}
|
||||
- "yes"
|
||||
- "no"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }" # value "yes"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "LoadFalse { dest: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }" # value "no"
|
||||
- "ObjectSet { obj: 0, key: 3, value: 4 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {true: "yes", false: "no"}
|
||||
|
||||
- note: object_null_key
|
||||
description: Object with null key
|
||||
example_rego: "{null: \"nothing\"}"
|
||||
literals:
|
||||
- {}
|
||||
- "nothing"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "LoadNull { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {null: "nothing"}
|
||||
|
||||
- note: object_empty_string_key
|
||||
description: Object with empty string key
|
||||
example_rego: "{\"\": \"empty_key\"}"
|
||||
literals:
|
||||
- {}
|
||||
- ""
|
||||
- "empty_key"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # empty string key
|
||||
- "Load { dest: 2, literal_idx: 2 }" # value
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"": "empty_key"}
|
||||
|
||||
- note: object_set_undefined_value
|
||||
description: Setting undefined value in object keeps it
|
||||
literals:
|
||||
- {}
|
||||
- "key"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
# r2 is undefined (never loaded)
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"key": "#undefined"}
|
||||
|
||||
- note: object_create_with_template
|
||||
description: ObjectCreate with pre-populated template
|
||||
literals:
|
||||
- {"existing": "value", "num": 42}
|
||||
- "new_key"
|
||||
- "new_value"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 2 }"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"existing": "value", "num": 42, "new_key": "new_value"}
|
||||
|
||||
- note: object_nested_structure
|
||||
description: Create deeply nested object structure
|
||||
example_rego: "{\"outer\": {\"middle\": {\"inner\": \"value\"}}}"
|
||||
literals:
|
||||
- {}
|
||||
- "outer"
|
||||
- "middle"
|
||||
- "inner"
|
||||
- "value"
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }" # outer
|
||||
- "Load { dest: 10, literal_idx: 0 }" # middle object
|
||||
- "Load { dest: 11, literal_idx: 0 }" # inner object
|
||||
- "Load { dest: 12, literal_idx: 3 }" # "inner" key
|
||||
- "Load { dest: 13, literal_idx: 4 }" # "value"
|
||||
- "ObjectSet { obj: 11, key: 12, value: 13 }" # {inner: "value"}
|
||||
- "Load { dest: 14, literal_idx: 2 }" # "middle" key
|
||||
- "ObjectSet { obj: 10, key: 14, value: 11 }" # {middle: {...}}
|
||||
- "Load { dest: 15, literal_idx: 1 }" # "outer" key
|
||||
- "ObjectSet { obj: 0, key: 15, value: 10 }" # {outer: {...}}
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"outer": {"middle": {"inner": "value"}}}
|
||||
|
||||
- note: object_mixed_value_types
|
||||
description: Object with values of different types
|
||||
example_rego: "{\"str\": \"text\", \"num\": 42, \"bool\": true, \"null\": null, \"arr\": [1, 2]}"
|
||||
literals:
|
||||
- {}
|
||||
- "str"
|
||||
- "text"
|
||||
- "num"
|
||||
- 42
|
||||
- "bool"
|
||||
- "null"
|
||||
- "arr"
|
||||
- [1, 2]
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # "str"
|
||||
- "Load { dest: 2, literal_idx: 2 }" # "text"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # "num"
|
||||
- "Load { dest: 4, literal_idx: 4 }" # 42
|
||||
- "ObjectSet { obj: 0, key: 3, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 5 }" # "bool"
|
||||
- "LoadTrue { dest: 6 }"
|
||||
- "ObjectSet { obj: 0, key: 5, value: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 6 }" # "null"
|
||||
- "LoadNull { dest: 8 }"
|
||||
- "ObjectSet { obj: 0, key: 7, value: 8 }"
|
||||
- "Load { dest: 9, literal_idx: 7 }" # "arr"
|
||||
- "Load { dest: 10, literal_idx: 8 }" # [1, 2]
|
||||
- "ObjectSet { obj: 0, key: 9, value: 10 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"str": "text", "num": 42, "bool": true, "null": null, "arr": [1, 2]}
|
||||
165
tests/rvm/vm/suites/predefined.yaml
Normal file
165
tests/rvm/vm/suites/predefined.yaml
Normal file
@@ -0,0 +1,165 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Predefined Global Bindings Test Suite
|
||||
# Tests Rego's predefined data and input global bindings
|
||||
# These bindings are always available in Rego policies
|
||||
|
||||
cases:
|
||||
- note: load_data_basic
|
||||
description: Test loading global data object
|
||||
data:
|
||||
users: ["alice", "bob"]
|
||||
config:
|
||||
debug: true
|
||||
timeout: 30
|
||||
input: null
|
||||
literals: ["users"]
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load "users" literal
|
||||
- "Index { dest: 2, container: 0, key: 1 }" # data.users
|
||||
- "Return { value: 2 }"
|
||||
want_result: ["alice", "bob"]
|
||||
|
||||
- note: load_input_basic
|
||||
description: Test loading global input object
|
||||
data: null
|
||||
input:
|
||||
request:
|
||||
method: "GET"
|
||||
path: "/api/users"
|
||||
user:
|
||||
id: 123
|
||||
role: "admin"
|
||||
literals: ["request", "method"]
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # Load "request" literal
|
||||
- "Index { dest: 2, container: 0, key: 1 }" # input.request
|
||||
- "Load { dest: 3, literal_idx: 1 }" # Load "method" literal
|
||||
- "Index { dest: 4, container: 2, key: 3 }" # input.request.method
|
||||
- "Return { value: 4 }"
|
||||
want_result: "GET"
|
||||
|
||||
- note: data_and_input_combined
|
||||
description: Test using both data and input in same expression
|
||||
data:
|
||||
permissions:
|
||||
admin: ["read", "write", "delete"]
|
||||
user: ["read"]
|
||||
input:
|
||||
user:
|
||||
role: "admin"
|
||||
literals: ["permissions", "user", "role"]
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }" # Load data
|
||||
- "LoadInput { dest: 1 }" # Load input
|
||||
- "Load { dest: 2, literal_idx: 1 }" # Load "user" literal
|
||||
- "Index { dest: 3, container: 1, key: 2 }" # input.user
|
||||
- "Load { dest: 4, literal_idx: 2 }" # Load "role" literal
|
||||
- "Index { dest: 5, container: 3, key: 4 }" # input.user.role
|
||||
- "Load { dest: 6, literal_idx: 0 }" # Load "permissions" literal
|
||||
- "Index { dest: 7, container: 0, key: 6 }" # data.permissions
|
||||
- "Index { dest: 8, container: 7, key: 5 }" # data.permissions[input.user.role]
|
||||
- "Return { value: 8 }"
|
||||
want_result: ["read", "write", "delete"]
|
||||
|
||||
- note: data_null_handling
|
||||
description: Test behavior when data is null
|
||||
data: null
|
||||
input:
|
||||
test: "value"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: input_null_handling
|
||||
description: Test behavior when input is null
|
||||
data:
|
||||
test: "value"
|
||||
input: null
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: nested_data_access
|
||||
description: Test deep nested data access
|
||||
data:
|
||||
api:
|
||||
v1:
|
||||
endpoints:
|
||||
users: "/api/v1/users"
|
||||
posts: "/api/v1/posts"
|
||||
input: null
|
||||
literals: ["api", "v1", "endpoints", "users"]
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # "api"
|
||||
- "Index { dest: 2, container: 0, key: 1 }" # data.api
|
||||
- "Load { dest: 3, literal_idx: 1 }" # "v1"
|
||||
- "Index { dest: 4, container: 2, key: 3 }" # data.api.v1
|
||||
- "Load { dest: 5, literal_idx: 2 }" # "endpoints"
|
||||
- "Index { dest: 6, container: 4, key: 5 }" # data.api.v1.endpoints
|
||||
- "Load { dest: 7, literal_idx: 3 }" # "users"
|
||||
- "Index { dest: 8, container: 6, key: 7 }" # data.api.v1.endpoints.users
|
||||
- "Return { value: 8 }"
|
||||
want_result: "/api/v1/users"
|
||||
|
||||
- note: array_access_with_input
|
||||
description: Test array indexing with input values
|
||||
data:
|
||||
colors: ["red", "green", "blue"]
|
||||
input:
|
||||
selected_index: 1
|
||||
literals: ["colors", "selected_index"]
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "LoadInput { dest: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 0 }" # "colors"
|
||||
- "Index { dest: 3, container: 0, key: 2 }" # data.colors
|
||||
- "Load { dest: 4, literal_idx: 1 }" # "selected_index"
|
||||
- "Index { dest: 5, container: 1, key: 4 }" # input.selected_index
|
||||
- "Index { dest: 6, container: 3, key: 5 }" # data.colors[input.selected_index]
|
||||
- "Return { value: 6 }"
|
||||
want_result: "green"
|
||||
|
||||
- note: data_only_access
|
||||
description: Test accessing data when input is not needed
|
||||
data:
|
||||
settings:
|
||||
theme: "dark"
|
||||
notifications: true
|
||||
input: null
|
||||
literals: ["settings", "theme"]
|
||||
instructions:
|
||||
- "LoadData { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # "settings"
|
||||
- "Index { dest: 2, container: 0, key: 1 }" # data.settings
|
||||
- "Load { dest: 3, literal_idx: 1 }" # "theme"
|
||||
- "Index { dest: 4, container: 2, key: 3 }" # data.settings.theme
|
||||
- "Return { value: 4 }"
|
||||
want_result: "dark"
|
||||
|
||||
- note: input_only_access
|
||||
description: Test accessing input when data is not needed
|
||||
data: null
|
||||
input:
|
||||
request:
|
||||
headers:
|
||||
authorization: "Bearer token123"
|
||||
literals: ["request", "headers", "authorization"]
|
||||
instructions:
|
||||
- "LoadInput { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # "request"
|
||||
- "Index { dest: 2, container: 0, key: 1 }" # input.request
|
||||
- "Load { dest: 3, literal_idx: 1 }" # "headers"
|
||||
- "Index { dest: 4, container: 2, key: 3 }" # input.request.headers
|
||||
- "Load { dest: 5, literal_idx: 2 }" # "authorization"
|
||||
- "Index { dest: 6, container: 4, key: 5 }" # input.request.headers.authorization
|
||||
- "Return { value: 6 }"
|
||||
want_result: "Bearer token123"
|
||||
217
tests/rvm/vm/suites/resource_limits.yaml
Normal file
217
tests/rvm/vm/suites/resource_limits.yaml
Normal file
@@ -0,0 +1,217 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Resource Limits Test Suite
|
||||
# Tests instruction count limits, recursion depth, and resource exhaustion scenarios
|
||||
# Verifies VM handles resource constraints gracefully
|
||||
|
||||
cases:
|
||||
- note: instruction_limit_in_simple_loop
|
||||
description: Instruction limit exceeded in simple loop
|
||||
example_rego: "some x in [1, 2, 3, 4, 5]; x > 0"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 0
|
||||
max_instructions: 10 # Limit low enough that execution exceeds it before completion
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 13
|
||||
loop_end: 17
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 0, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 4 }"
|
||||
- "ArrayPush { arr: 0, value: 5 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 13, literal_idx: 5 }"
|
||||
- "Gt { dest: 14, left: 11, right: 13 }"
|
||||
- "AssertCondition { condition: 14 }"
|
||||
- "LoopNext { body_start: 13, loop_end: 17 }"
|
||||
- "Return { value: 12 }"
|
||||
want_error: "exceeded maximum instruction limit"
|
||||
|
||||
- note: instruction_limit_in_comprehension
|
||||
description: Instruction limit exceeded during comprehension
|
||||
example_rego: "[x | x := [1, 2, 3, 4, 5][_]]"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
max_instructions: 25
|
||||
instruction_params:
|
||||
comprehension_begin_params:
|
||||
- mode: "Array"
|
||||
collection_reg: 0
|
||||
result_reg: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
body_start: 13
|
||||
comprehension_end: 17
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 6
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 13
|
||||
loop_end: 17
|
||||
instructions:
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 6, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 6, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 6, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 6, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 4 }"
|
||||
- "ArrayPush { arr: 6, value: 5 }"
|
||||
- "ComprehensionBegin { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "ComprehensionYield { value_reg: 11 }"
|
||||
- "LoopNext { body_start: 13, loop_end: 17 }"
|
||||
- "ComprehensionEnd"
|
||||
- "Return { value: 0 }"
|
||||
want_error: "exceeded maximum instruction limit"
|
||||
|
||||
- note: instruction_limit_in_nested_loops
|
||||
description: Instruction limit exceeded in nested loops
|
||||
example_rego: "some x in [1, 2]; some y in [3, 4]; x + y > 0"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 0
|
||||
max_instructions: 30
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 5
|
||||
loop_end: 18
|
||||
- mode: "Any"
|
||||
collection: 2
|
||||
key_reg: 20
|
||||
value_reg: 21
|
||||
result_reg: 22
|
||||
body_start: 8
|
||||
loop_end: 16
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 3 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "ArrayNew { dest: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "ArrayPush { arr: 2, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "ArrayPush { arr: 2, value: 5 }"
|
||||
- "LoopStart { params_index: 1 }"
|
||||
- "Add { dest: 30, left: 11, right: 21 }"
|
||||
- "Load { dest: 31, literal_idx: 4 }"
|
||||
- "Gt { dest: 32, left: 30, right: 31 }"
|
||||
- "AssertCondition { condition: 32 }"
|
||||
- "LoopNext { body_start: 8, loop_end: 16 }"
|
||||
- "AssertCondition { condition: 22 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 18 }"
|
||||
- "Return { value: 12 }"
|
||||
want_error: "exceeded maximum instruction limit"
|
||||
|
||||
- note: large_literal_table_access
|
||||
description: Access literal near u16 bounds (valid case)
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: 42
|
||||
|
||||
- note: literal_index_out_of_bounds
|
||||
description: Literal index beyond table bounds should error
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 999 }"
|
||||
- "Return { value: 0 }"
|
||||
want_error: "Literal index 999 out of bounds"
|
||||
|
||||
- note: instruction_limit_with_early_return
|
||||
description: Instruction limit allows early successful completion
|
||||
example_rego: "some x in [1, 2]; x == 1"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
max_instructions: 200 # Allow enough headroom for both execution modes
|
||||
instruction_params:
|
||||
loop_params:
|
||||
- mode: "Any"
|
||||
collection: 0
|
||||
key_reg: 10
|
||||
value_reg: 11
|
||||
result_reg: 12
|
||||
body_start: 6
|
||||
loop_end: 9
|
||||
instructions:
|
||||
- "ArrayNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "ArrayPush { arr: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "ArrayPush { arr: 0, value: 2 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Load { dest: 13, literal_idx: 0 }"
|
||||
- "Eq { dest: 14, left: 11, right: 13 }"
|
||||
- "AssertCondition { condition: 14 }"
|
||||
- "LoopNext { body_start: 6, loop_end: 9 }"
|
||||
- "Return { value: 12 }"
|
||||
want_result: true
|
||||
|
||||
- note: many_registers_usage
|
||||
description: Test using high register numbers near upper bound without error
|
||||
example_rego: "Store values in registers near VM upper bound"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 25, literal_idx: 1 }"
|
||||
- "Load { dest: 49, literal_idx: 2 }"
|
||||
- "Add { dest: 30, left: 0, right: 25 }"
|
||||
- "Add { dest: 48, left: 30, right: 49 }"
|
||||
- "Return { value: 48 }"
|
||||
want_result: 6
|
||||
|
||||
- note: zero_instruction_limit
|
||||
description: Zero instruction limit should error immediately
|
||||
literals:
|
||||
- 42
|
||||
max_instructions: 0
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_error: "exceeded maximum instruction limit"
|
||||
423
tests/rvm/vm/suites/serialization.yaml
Normal file
423
tests/rvm/vm/suites/serialization.yaml
Normal file
@@ -0,0 +1,423 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Serialization Test Suite
|
||||
# Tests round-trip serialization of compiled RVM programs
|
||||
# Covers all instruction types, large programs, and edge cases
|
||||
|
||||
cases:
|
||||
- note: serialization_basic_arithmetic
|
||||
description: Serialize and deserialize simple arithmetic program
|
||||
example_rego: "5 + 3"
|
||||
literals:
|
||||
- 5
|
||||
- 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 8
|
||||
|
||||
- note: serialization_comparisons
|
||||
description: Serialize comparison instructions
|
||||
example_rego: "10 > 5 && 3 < 7"
|
||||
literals:
|
||||
- 10
|
||||
- 5
|
||||
- 3
|
||||
- 7
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Gt { dest: 2, left: 0, right: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "Lt { dest: 5, left: 3, right: 4 }"
|
||||
- "And { dest: 6, left: 2, right: 5 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: true
|
||||
|
||||
- note: serialization_array_create
|
||||
description: Serialize ArrayCreate with instruction_params
|
||||
example_rego: "[1, 2, 3]"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [1, 2, 3]
|
||||
|
||||
- note: serialization_object_create
|
||||
description: Serialize ObjectCreate with complex params
|
||||
example_rego: "{\"a\": 1, \"b\": 2}"
|
||||
literals:
|
||||
- "a"
|
||||
- 1
|
||||
- "b"
|
||||
- 2
|
||||
- {}
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 4
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [1, 2]
|
||||
- [3, 4]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"a": 1, "b": 2}
|
||||
|
||||
- note: serialization_set_operations
|
||||
description: Serialize set creation and operations
|
||||
example_rego: "{1, 2, 3}"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- note: serialization_loop_foreach
|
||||
description: Serialize ForEach loop with params
|
||||
example_rego: "[x | x = [1, 2, 3][_]]"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3]
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 9
|
||||
body_start: 6
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "ArrayNew { dest: 6 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "ArrayPush { arr: 6, value: 5 }"
|
||||
- "LoopNext { body_start: 6, loop_end: 8 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: [1, 2, 3]
|
||||
|
||||
- note: serialization_loop_any
|
||||
description: Serialize Any loop
|
||||
example_rego: "some x in [1, 2, 3]; x > 2"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3]
|
||||
loop_params:
|
||||
- mode: "Existential"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 6
|
||||
body_start: 5
|
||||
loop_end: 8
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Gt { dest: 8, left: 5, right: 2 }"
|
||||
- "AssertCondition { condition: 8 }"
|
||||
- "LoopNext { body_start: 5, loop_end: 8 }"
|
||||
- "Return { value: 6 }"
|
||||
want_result: true
|
||||
|
||||
- note: serialization_comprehension_array
|
||||
description: Serialize array comprehension
|
||||
example_rego: "[x * 2 | x = [1, 2, 3][_]]"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3]
|
||||
loop_params:
|
||||
- mode: "ForEach"
|
||||
collection: 0
|
||||
key_reg: 4
|
||||
value_reg: 5
|
||||
result_reg: 9
|
||||
body_start: 6
|
||||
loop_end: 9
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "ArrayNew { dest: 7 }"
|
||||
- "LoopStart { params_index: 0 }"
|
||||
- "Mul { dest: 6, left: 5, right: 2 }"
|
||||
- "ArrayPush { arr: 7, value: 6 }"
|
||||
- "LoopNext { body_start: 6, loop_end: 9 }"
|
||||
- "Return { value: 7 }"
|
||||
want_result: [2, 4, 6]
|
||||
|
||||
- note: serialization_indexed_access
|
||||
description: Serialize indexing instructions
|
||||
example_rego: "data.users[0].name"
|
||||
literals:
|
||||
- {"users": [{"name": "Alice"}, {"name": "Bob"}]}
|
||||
- "users"
|
||||
- 0
|
||||
- "name"
|
||||
instruction_params:
|
||||
chained_index_params:
|
||||
- dest: 4
|
||||
root: 0
|
||||
path_components:
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
- literal_idx: 3
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 3 }"
|
||||
- "ChainedIndex { params_index: 0 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: "Alice"
|
||||
|
||||
- note: serialization_conditional_branching
|
||||
description: Serialize conditional instructions
|
||||
example_rego: "if true then 1 else 2"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "AssertCondition { condition: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_result: 1
|
||||
|
||||
- note: serialization_null_and_undefined
|
||||
description: Serialize null and undefined handling
|
||||
example_rego: "null"
|
||||
literals: []
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: null
|
||||
|
||||
- note: serialization_mixed_types
|
||||
description: Serialize program with all value types
|
||||
example_rego: "[1, \"text\", true, false, null, [2, 3], {\"k\": \"v\"}]"
|
||||
literals:
|
||||
- 1
|
||||
- "text"
|
||||
- 2
|
||||
- 3
|
||||
- "k"
|
||||
- "v"
|
||||
- {}
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 6
|
||||
elements: [3, 4]
|
||||
- dest: 0
|
||||
elements: [1, 2, 7, 8, 9, 6, 10]
|
||||
object_create_params:
|
||||
- dest: 10
|
||||
template_literal_idx: 6
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [5, 11]
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "LoadTrue { dest: 7 }"
|
||||
- "LoadFalse { dest: 8 }"
|
||||
- "LoadNull { dest: 9 }"
|
||||
- "Load { dest: 5, literal_idx: 4 }"
|
||||
- "Load { dest: 11, literal_idx: 5 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ArrayCreate { params_index: 1 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [1, "text", true, false, null, [2, 3], {"k": "v"}]
|
||||
|
||||
- note: serialization_large_program_50_instructions
|
||||
description: Large program with many instructions (stress test)
|
||||
example_rego: "Complex computation chain"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "Mul { dest: 4, left: 2, right: 3 }"
|
||||
- "Load { dest: 5, literal_idx: 3 }"
|
||||
- "Sub { dest: 6, left: 4, right: 5 }"
|
||||
- "Load { dest: 7, literal_idx: 4 }"
|
||||
- "Div { dest: 8, left: 6, right: 7 }"
|
||||
- "Load { dest: 9, literal_idx: 0 }"
|
||||
- "Add { dest: 10, left: 8, right: 9 }"
|
||||
- "Load { dest: 11, literal_idx: 1 }"
|
||||
- "Mul { dest: 12, left: 10, right: 11 }"
|
||||
- "Load { dest: 13, literal_idx: 2 }"
|
||||
- "Add { dest: 14, left: 12, right: 13 }"
|
||||
- "Load { dest: 15, literal_idx: 3 }"
|
||||
- "Sub { dest: 16, left: 14, right: 15 }"
|
||||
- "Load { dest: 17, literal_idx: 4 }"
|
||||
- "Mul { dest: 18, left: 16, right: 17 }"
|
||||
- "Load { dest: 19, literal_idx: 0 }"
|
||||
- "Div { dest: 20, left: 18, right: 19 }"
|
||||
- "Load { dest: 21, literal_idx: 1 }"
|
||||
- "Add { dest: 22, left: 20, right: 21 }"
|
||||
- "Load { dest: 23, literal_idx: 2 }"
|
||||
- "Mul { dest: 24, left: 22, right: 23 }"
|
||||
- "Load { dest: 25, literal_idx: 3 }"
|
||||
- "Sub { dest: 26, left: 24, right: 25 }"
|
||||
- "Load { dest: 27, literal_idx: 4 }"
|
||||
- "Add { dest: 28, left: 26, right: 27 }"
|
||||
- "Load { dest: 29, literal_idx: 0 }"
|
||||
- "Mul { dest: 30, left: 28, right: 29 }"
|
||||
- "Load { dest: 31, literal_idx: 1 }"
|
||||
- "Div { dest: 32, left: 30, right: 31 }"
|
||||
- "Load { dest: 33, literal_idx: 2 }"
|
||||
- "Add { dest: 34, left: 32, right: 33 }"
|
||||
- "Load { dest: 35, literal_idx: 3 }"
|
||||
- "Sub { dest: 36, left: 34, right: 35 }"
|
||||
- "Load { dest: 37, literal_idx: 4 }"
|
||||
- "Mul { dest: 38, left: 36, right: 37 }"
|
||||
- "Load { dest: 39, literal_idx: 0 }"
|
||||
- "Add { dest: 40, left: 38, right: 39 }"
|
||||
- "Load { dest: 41, literal_idx: 1 }"
|
||||
- "Sub { dest: 42, left: 40, right: 41 }"
|
||||
- "Load { dest: 43, literal_idx: 2 }"
|
||||
- "Mul { dest: 44, left: 42, right: 43 }"
|
||||
- "Load { dest: 45, literal_idx: 3 }"
|
||||
- "Div { dest: 46, left: 44, right: 45 }"
|
||||
- "Load { dest: 47, literal_idx: 4 }"
|
||||
- "Add { dest: 48, left: 46, right: 47 }"
|
||||
- "Return { value: 48 }"
|
||||
want_result: 98
|
||||
|
||||
- note: serialization_large_literal_table
|
||||
description: Program with many literal values
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
- "a"
|
||||
- "b"
|
||||
- "c"
|
||||
- "d"
|
||||
- "e"
|
||||
- [1, 2, 3]
|
||||
- {"key": "value"}
|
||||
- true
|
||||
- false
|
||||
- null
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 5 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 10 }"
|
||||
- "Load { dest: 4, literal_idx: 15 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: 7
|
||||
|
||||
- note: serialization_nested_structures
|
||||
description: Serialize deeply nested data structures
|
||||
example_rego: "[{\"a\": [1, {\"b\": [2, 3]}]}]"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- "b"
|
||||
- "a"
|
||||
- {}
|
||||
instruction_params:
|
||||
array_create_params:
|
||||
- dest: 3
|
||||
elements: [2, 4]
|
||||
- dest: 1
|
||||
elements: [5, 6]
|
||||
- dest: 0
|
||||
elements: [7]
|
||||
object_create_params:
|
||||
- dest: 6
|
||||
template_literal_idx: 5
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [8, 3]
|
||||
- dest: 7
|
||||
template_literal_idx: 5
|
||||
literal_key_fields: []
|
||||
fields:
|
||||
- [9, 1]
|
||||
instructions:
|
||||
- "Load { dest: 5, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 4, literal_idx: 2 }"
|
||||
- "ArrayCreate { params_index: 0 }"
|
||||
- "Load { dest: 8, literal_idx: 3 }"
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "ArrayCreate { params_index: 1 }"
|
||||
- "Load { dest: 9, literal_idx: 4 }"
|
||||
- "ObjectCreate { params_index: 1 }"
|
||||
- "ArrayCreate { params_index: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: [{"a": [1, {"b": [2, 3]}]}]
|
||||
284
tests/rvm/vm/suites/set_operations.yaml
Normal file
284
tests/rvm/vm/suites/set_operations.yaml
Normal file
@@ -0,0 +1,284 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Set Operations Test Suite
|
||||
# Tests set creation, deduplication, and membership testing
|
||||
# Covers complex value deduplication, nested sets, and undefined handling
|
||||
|
||||
cases:
|
||||
- note: set_deduplication_simple
|
||||
description: Set automatically deduplicates simple values
|
||||
example_rego: "{1, 2, 1, 3, 2}"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # 1
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }" # 2
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "SetAdd { set: 0, value: 1 }" # 1 again (duplicate)
|
||||
- "Load { dest: 3, literal_idx: 2 }" # 3
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "SetAdd { set: 0, value: 2 }" # 2 again (duplicate)
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- note: set_deduplication_objects
|
||||
description: Set deduplicates identical objects
|
||||
example_rego: "{{\"a\": 1}, {\"b\": 2}, {\"a\": 1}}"
|
||||
literals:
|
||||
- {"a": 1}
|
||||
- {"b": 2}
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # {"a": 1}
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }" # {"b": 2}
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "SetAdd { set: 0, value: 1 }" # {"a": 1} again (duplicate)
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- {"a": 1}
|
||||
- {"b": 2}
|
||||
|
||||
- note: set_deduplication_arrays
|
||||
description: Set deduplicates identical arrays
|
||||
example_rego: "{[1, 2], [3, 4], [1, 2]}"
|
||||
literals:
|
||||
- [1, 2]
|
||||
- [3, 4]
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # [1, 2]
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }" # [3, 4]
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "SetAdd { set: 0, value: 1 }" # [1, 2] again (duplicate)
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- [1, 2]
|
||||
- [3, 4]
|
||||
|
||||
- note: set_with_mixed_types
|
||||
description: Set can contain different types
|
||||
example_rego: "{1, \"text\", true, null, [1, 2], {\"a\": 1}}"
|
||||
literals:
|
||||
- 1
|
||||
- "text"
|
||||
- [1, 2]
|
||||
- {"a": 1}
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }" # 1
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }" # "text"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "LoadTrue { dest: 3 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "LoadNull { dest: 4 }"
|
||||
- "SetAdd { set: 0, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 2 }" # [1, 2]
|
||||
- "SetAdd { set: 0, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 3 }" # {"a": 1}
|
||||
- "SetAdd { set: 0, value: 6 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- "text"
|
||||
- true
|
||||
- null
|
||||
- [1, 2]
|
||||
- {"a": 1}
|
||||
|
||||
- note: set_contains_simple
|
||||
description: Contains check on set with simple values
|
||||
example_rego: "2 in {1, 2, 3}"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Contains { dest: 4, collection: 0, value: 2 }"
|
||||
- "Return { value: 4 }"
|
||||
want_result: true
|
||||
|
||||
- note: set_contains_object
|
||||
description: Contains check with object value
|
||||
example_rego: "{\"a\": 1} in {{\"a\": 1}, {\"b\": 2}}"
|
||||
literals:
|
||||
- {"a": 1}
|
||||
- {"b": 2}
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Contains { dest: 3, collection: 0, value: 1 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: true
|
||||
|
||||
- note: set_contains_not_found
|
||||
description: Contains returns false when value not in set
|
||||
example_rego: "5 in {1, 2, 3}"
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 5
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "Contains { dest: 5, collection: 0, value: 4 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: false
|
||||
|
||||
- note: set_create_from_registers
|
||||
description: SetCreate instruction creates set from multiple registers
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
instruction_params:
|
||||
set_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3, 1] # Duplicate 1
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- note: set_create_with_undefined_element
|
||||
description: SetCreate with undefined element returns undefined (short-circuit)
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- "#undefined"
|
||||
instruction_params:
|
||||
set_create_params:
|
||||
- dest: 0
|
||||
elements: [1, 2, 3] # r3 is undefined
|
||||
instructions:
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }" # r3 is explicitly undefined
|
||||
- "SetCreate { params_index: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: set_large_collection
|
||||
description: Set with many elements (stress test)
|
||||
literals:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 2 }"
|
||||
- "SetAdd { set: 0, value: 3 }"
|
||||
- "Load { dest: 4, literal_idx: 3 }"
|
||||
- "SetAdd { set: 0, value: 4 }"
|
||||
- "Load { dest: 5, literal_idx: 4 }"
|
||||
- "SetAdd { set: 0, value: 5 }"
|
||||
- "Load { dest: 6, literal_idx: 5 }"
|
||||
- "SetAdd { set: 0, value: 6 }"
|
||||
- "Load { dest: 7, literal_idx: 6 }"
|
||||
- "SetAdd { set: 0, value: 7 }"
|
||||
- "Load { dest: 8, literal_idx: 7 }"
|
||||
- "SetAdd { set: 0, value: 8 }"
|
||||
- "Load { dest: 9, literal_idx: 8 }"
|
||||
- "SetAdd { set: 0, value: 9 }"
|
||||
- "Load { dest: 10, literal_idx: 9 }"
|
||||
- "SetAdd { set: 0, value: 10 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
- 8
|
||||
- 9
|
||||
- 10
|
||||
|
||||
- note: set_empty
|
||||
description: Empty set creation
|
||||
example_rego: "set()"
|
||||
literals: []
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!: []
|
||||
|
||||
- note: set_nested_in_set
|
||||
description: Set containing other sets
|
||||
example_rego: "{{1, 2}, {3, 4}}"
|
||||
literals:
|
||||
- set!:
|
||||
- 1
|
||||
- 2
|
||||
- set!:
|
||||
- 3
|
||||
- 4
|
||||
instructions:
|
||||
- "SetNew { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "SetAdd { set: 0, value: 1 }"
|
||||
- "Load { dest: 2, literal_idx: 1 }"
|
||||
- "SetAdd { set: 0, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result:
|
||||
set!:
|
||||
-
|
||||
set!:
|
||||
- 1
|
||||
- 2
|
||||
-
|
||||
set!:
|
||||
- 3
|
||||
- 4
|
||||
224
tests/rvm/vm/suites/type_errors.yaml
Normal file
224
tests/rvm/vm/suites/type_errors.yaml
Normal file
@@ -0,0 +1,224 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
# Type Errors Test Suite
|
||||
# Tests graceful error handling for type mismatches across instruction families
|
||||
# Verifies that VM returns appropriate errors instead of panicking
|
||||
|
||||
cases:
|
||||
# Mixed-type arithmetic operations
|
||||
- note: arithmetic_add_string_to_int
|
||||
description: Adding string to int should error gracefully
|
||||
example_rego: "1 + \"text\""
|
||||
literals:
|
||||
- 1
|
||||
- "text"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot add"
|
||||
|
||||
- note: arithmetic_mul_bool_by_int
|
||||
description: Multiplying boolean by int should error
|
||||
example_rego: "true * 5"
|
||||
literals:
|
||||
- 5
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Mul { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot multiply"
|
||||
|
||||
- note: arithmetic_div_null_by_int
|
||||
description: Dividing null by int should error
|
||||
example_rego: "null / 2"
|
||||
literals:
|
||||
- 2
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Div { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot divide"
|
||||
|
||||
- note: arithmetic_sub_array_from_int
|
||||
description: Subtracting array from int should error
|
||||
example_rego: "10 - [1, 2]"
|
||||
literals:
|
||||
- 10
|
||||
- [1, 2]
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Sub { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot subtract"
|
||||
|
||||
- note: arithmetic_add_object_to_int
|
||||
description: Adding object to int should error
|
||||
example_rego: "5 + {\"a\": 1}"
|
||||
literals:
|
||||
- 5
|
||||
- {"a": 1}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Add { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "Cannot add"
|
||||
|
||||
# Logical operations on non-booleans
|
||||
- note: logical_and_string_with_bool
|
||||
description: AND with string operand should error
|
||||
example_rego: "\"text\" && true"
|
||||
literals:
|
||||
- "text"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "And { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "#undefined"
|
||||
|
||||
- note: logical_or_int_with_bool
|
||||
description: OR with int operand should error
|
||||
example_rego: "42 || false"
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "LoadFalse { dest: 1 }"
|
||||
- "Or { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_error: "#undefined"
|
||||
|
||||
- note: logical_not_int
|
||||
description: NOT with int operand should error
|
||||
example_rego: "!42"
|
||||
literals:
|
||||
- 42
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Not { dest: 1, operand: 0 }"
|
||||
- "Return { value: 1 }"
|
||||
want_error: "#undefined"
|
||||
|
||||
# Invalid indexing operations
|
||||
- note: index_int_with_string_key
|
||||
description: Indexing int with string should return undefined
|
||||
example_rego: "123[\"key\"]"
|
||||
literals:
|
||||
- 123
|
||||
- "key"
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: index_null_with_int
|
||||
description: Indexing null should return undefined
|
||||
example_rego: "null[0]"
|
||||
literals:
|
||||
- 0
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: index_bool_with_string
|
||||
description: Indexing boolean should return undefined
|
||||
example_rego: "true.field"
|
||||
literals:
|
||||
- "field"
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: index_string_with_object_key
|
||||
description: Indexing string with object key should return undefined
|
||||
example_rego: "\"text\"[{}]"
|
||||
literals:
|
||||
- "text"
|
||||
- {}
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Index { dest: 2, container: 0, key: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
# Contains with incompatible types
|
||||
- note: contains_in_int
|
||||
description: Contains check on int should return false
|
||||
example_rego: "1 in 123"
|
||||
literals:
|
||||
- 123
|
||||
- 1
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Contains { dest: 2, collection: 0, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
- note: contains_in_bool
|
||||
description: Contains check on boolean should return false
|
||||
example_rego: "1 in true"
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Contains { dest: 2, collection: 0, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
- note: contains_in_null
|
||||
description: Contains check on null should return false
|
||||
example_rego: "1 in null"
|
||||
literals:
|
||||
- 1
|
||||
instructions:
|
||||
- "LoadNull { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Contains { dest: 2, collection: 0, value: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
|
||||
# Comparison operations with incompatible types
|
||||
- note: compare_string_lt_int
|
||||
description: Comparing string < int returns ordering result in non-strict; errors in strict
|
||||
example_rego: "\"abc\" < 5"
|
||||
literals:
|
||||
- "abc"
|
||||
- 5
|
||||
instructions:
|
||||
- "Load { dest: 0, literal_idx: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }"
|
||||
- "Lt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
want_error_strict: "#undefined"
|
||||
|
||||
- note: compare_bool_gt_array
|
||||
description: Comparing bool > array returns ordering result in non-strict; errors in strict
|
||||
example_rego: "true > [1, 2]"
|
||||
literals:
|
||||
- [1, 2]
|
||||
instructions:
|
||||
- "LoadTrue { dest: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 0 }"
|
||||
- "Gt { dest: 2, left: 0, right: 1 }"
|
||||
- "Return { value: 2 }"
|
||||
want_result: false
|
||||
want_error_strict: "#undefined"
|
||||
274
tests/rvm/vm/suites/virtual_data_lookup.yaml
Normal file
274
tests/rvm/vm/suites/virtual_data_lookup.yaml
Normal file
@@ -0,0 +1,274 @@
|
||||
# Virtual Data Lookup Test Suite
|
||||
# Exercises VirtualDataDocumentLookup across data/rule blending and errors.
|
||||
|
||||
cases:
|
||||
- note: virtual_lookup_base_data_only
|
||||
description: Lookup resolves to base data when no rules override
|
||||
data:
|
||||
services:
|
||||
api:
|
||||
enabled: true
|
||||
literals:
|
||||
- "services"
|
||||
- "api"
|
||||
- "enabled"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: true
|
||||
|
||||
- note: virtual_lookup_rule_override
|
||||
description: Lookup combines rule result overriding base data
|
||||
data:
|
||||
services:
|
||||
api:
|
||||
enabled: false
|
||||
literals:
|
||||
- "services"
|
||||
- "api"
|
||||
- "enabled"
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [2]
|
||||
rule_tree:
|
||||
data:
|
||||
services:
|
||||
api:
|
||||
enabled: 0
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "LoadTrue { dest: 1 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: true
|
||||
|
||||
- note: virtual_lookup_rule_subobject
|
||||
description: Lookup merges nested rule tree subobjects
|
||||
data:
|
||||
tenants:
|
||||
alpha:
|
||||
feature: "beta"
|
||||
literals:
|
||||
- "tenants"
|
||||
- "alpha"
|
||||
- "feature"
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [2]
|
||||
rule_tree:
|
||||
data:
|
||||
tenants:
|
||||
alpha:
|
||||
extra: 0
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 4
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 4 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "RuleReturn {}"
|
||||
want_result:
|
||||
feature: "beta"
|
||||
extra: "feature"
|
||||
|
||||
- note: virtual_lookup_invalid_rule_index
|
||||
description: Invalid rule index produces corresponding error
|
||||
literals:
|
||||
- "services"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 1
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
rule_tree:
|
||||
data:
|
||||
services: 99
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
want_error: "Rule index 99 out of bounds"
|
||||
|
||||
- note: virtual_lookup_rule_data_conflict
|
||||
description: Rule completely replaces data at the same path
|
||||
data:
|
||||
config:
|
||||
mode: "data_value"
|
||||
literals:
|
||||
- "config"
|
||||
- "mode"
|
||||
- "rule_value"
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [3]
|
||||
rule_tree:
|
||||
data:
|
||||
config:
|
||||
mode: 0
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 2 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: "rule_value"
|
||||
|
||||
- note: virtual_lookup_deep_path_5_levels
|
||||
description: Lookup with 5-level deep path
|
||||
data:
|
||||
level1:
|
||||
level2:
|
||||
level3:
|
||||
level4:
|
||||
level5: "deep_value"
|
||||
literals:
|
||||
- "level1"
|
||||
- "level2"
|
||||
- "level3"
|
||||
- "level4"
|
||||
- "level5"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 5
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
- literal_idx: 3
|
||||
- literal_idx: 4
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 5 }"
|
||||
want_result: "deep_value"
|
||||
|
||||
- note: virtual_lookup_deep_path_with_array_indices
|
||||
description: Lookup path with array index components
|
||||
data:
|
||||
containers:
|
||||
- name: "first"
|
||||
- name: "second"
|
||||
- name: "third"
|
||||
literals:
|
||||
- "containers"
|
||||
- 1
|
||||
- "name"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: "second"
|
||||
|
||||
- note: virtual_lookup_mixed_data_rules_deep
|
||||
description: Deep lookup with rules at multiple levels
|
||||
data:
|
||||
org:
|
||||
dept:
|
||||
team:
|
||||
lead: "data_lead"
|
||||
literals:
|
||||
- "org"
|
||||
- "dept"
|
||||
- "team"
|
||||
- "lead"
|
||||
- "rule_lead"
|
||||
rule_infos:
|
||||
- rule_type: Complete
|
||||
definitions:
|
||||
- [3]
|
||||
rule_tree:
|
||||
data:
|
||||
org:
|
||||
dept:
|
||||
team:
|
||||
lead: 0
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 5
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
- literal_idx: 3
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 5 }"
|
||||
- "RuleInit { result_reg: 1, rule_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 4 }"
|
||||
- "RuleReturn {}"
|
||||
want_result: "rule_lead"
|
||||
|
||||
- note: virtual_lookup_nonexistent_deep_path
|
||||
description: Lookup of non-existent deep path returns undefined
|
||||
data:
|
||||
root:
|
||||
child: "value"
|
||||
literals:
|
||||
- "root"
|
||||
- "nonexistent"
|
||||
- "path"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: "#undefined"
|
||||
|
||||
- note: virtual_lookup_partial_match_deep_path
|
||||
description: Partial path match returns undefined (not intermediate object)
|
||||
data:
|
||||
a:
|
||||
b:
|
||||
c: "value"
|
||||
literals:
|
||||
- "a"
|
||||
- "b"
|
||||
- "d"
|
||||
instruction_params:
|
||||
virtual_data_document_lookup_params:
|
||||
- dest: 3
|
||||
path_components:
|
||||
- literal_idx: 0
|
||||
- literal_idx: 1
|
||||
- literal_idx: 2
|
||||
instructions:
|
||||
- "VirtualDataDocumentLookup { params_index: 0 }"
|
||||
- "Return { value: 3 }"
|
||||
want_result: "#undefined"
|
||||
Reference in New Issue
Block a user