From f97e022eb7d1a095a2e2db6b07681eb1409e2679 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 26 Mar 2026 14:08:32 +0200 Subject: [PATCH 01/31] feat: add test_helpers module (error_utils, test_utils) behind function_runner flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create vm/src/test_helpers/ with error_utils.rs and test_utils.rs - Move from cairo_test_suite/ (fix filename typo: utlis → utils) - Fix crate:: import paths (were cairo_vm:: when outside the crate) - Fix $crate in macro_export macro (clippy::crate_in_macro_def) - Simplify load_cairo_program! path using with_file_name() - Gate module behind function_runner feature in lib.rs Co-Authored-By: Claude Sonnet 4.6 --- vm/src/lib.rs | 3 + vm/src/test_helpers/error_utils.rs | 170 +++++++++++++++++++++++++++++ vm/src/test_helpers/mod.rs | 4 + vm/src/test_helpers/test_utils.rs | 53 +++++++++ 4 files changed, 230 insertions(+) create mode 100644 vm/src/test_helpers/error_utils.rs create mode 100644 vm/src/test_helpers/mod.rs create mode 100644 vm/src/test_helpers/test_utils.rs diff --git a/vm/src/lib.rs b/vm/src/lib.rs index ab2bbc2e5f..2406f05bf1 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -26,6 +26,9 @@ pub mod types; pub mod utils; pub mod vm; +#[cfg(feature = "function_runner")] +pub mod test_helpers; + // TODO: use `Felt` directly pub use starknet_types_core::felt::Felt as Felt252; diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs new file mode 100644 index 0000000000..51cf3b5751 --- /dev/null +++ b/vm/src/test_helpers/error_utils.rs @@ -0,0 +1,170 @@ +//! Test utilities for Cairo VM result assertions. + +use crate::vm::errors::{ + cairo_run_errors::CairoRunError, hint_errors::HintError, vm_errors::VirtualMachineError, + vm_exception::VmException, +}; + +/// Asserts VM result is `Ok` or matches an error pattern. +#[macro_export] +macro_rules! assert_vm_result { + ($res:expr, ok $(,)?) => {{ + match &$res { + Ok(_) => {} + Err(e) => panic!("Expected Ok, got Err: {:#?}", e), + } + }}; + + ($res:expr, err $pat:pat $(,)?) => {{ + match &$res { + Ok(v) => panic!("Expected Err, got Ok: {v:?}"), + Err(e) => assert!( + matches!(e, $pat), + "Unexpected error variant.\nExpected: {}\nGot: {:#?}", + stringify!($pat), + e + ), + } + }}; + + ($res:expr, err $pat:pat if $guard:expr $(,)?) => {{ + match &$res { + Ok(v) => panic!("Expected Err, got Ok: {v:?}"), + Err(e) => assert!( + matches!(e, $pat if $guard), + "Unexpected error variant.\nExpected: {} (with guard)\nGot: {:#?}", + stringify!($pat), + e + ), + } + }}; +} + +/// Type alias for check functions that validate test results. +pub type VmCheck = fn(&std::result::Result); + +/// Asserts that the result is `Ok`. +pub fn expect_ok(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!(res, ok); +} + +/// Asserts that the result is `HintError::AssertNotZero`. +pub fn expect_hint_assert_not_zero(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::AssertNotZero(_))) + ); +} + +/// Asserts that the result is `HintError::AssertNotEqualFail`. +pub fn expect_assert_not_equal_fail(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::AssertNotEqualFail(_))) + ); +} + +/// Asserts that the result is `HintError::Internal(VirtualMachineError::DiffTypeComparison)`. +pub fn expect_diff_type_comparison(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::Internal(VirtualMachineError::DiffTypeComparison(_)))) + ); +} + +/// Asserts that the result is `HintError::Internal(VirtualMachineError::DiffIndexComp)`. +pub fn expect_diff_index_comp(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::Internal(VirtualMachineError::DiffIndexComp(_)))) + ); +} + +/// Asserts that the result is `HintError::ValueOutside250BitRange`. +pub fn expect_hint_value_outside_250_bit_range(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::ValueOutside250BitRange(_))) + ); +} + +/// Asserts that the result is `HintError::NonLeFelt252`. +pub fn expect_non_le_felt252(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::NonLeFelt252(_))) + ); +} + +/// Asserts that the result is `HintError::AssertLtFelt252`. +pub fn expect_assert_lt_felt252(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::AssertLtFelt252(_))) + ); +} + +/// Asserts that the result is `HintError::ValueOutsideValidRange`. +pub fn expect_hint_value_outside_valid_range(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::ValueOutsideValidRange(_))) + ); +} + +/// Asserts that the result is `HintError::OutOfValidRange`. +pub fn expect_hint_out_of_valid_range(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::OutOfValidRange(_))) + ); +} + +/// Asserts that the result is `HintError::SplitIntNotZero`. +pub fn expect_split_int_not_zero(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::SplitIntNotZero)) + ); +} + +/// Asserts that the result is `HintError::SplitIntLimbOutOfRange`. +pub fn expect_split_int_limb_out_of_range(res: &std::result::Result<(), CairoRunError>) { + assert_vm_result!( + res, + err CairoRunError::VmException(VmException { + inner_exc: VirtualMachineError::Hint(boxed), + .. + }) if matches!(boxed.as_ref(), (_, HintError::SplitIntLimbOutOfRange(_))) + ); +} diff --git a/vm/src/test_helpers/mod.rs b/vm/src/test_helpers/mod.rs new file mode 100644 index 0000000000..438189d0ad --- /dev/null +++ b/vm/src/test_helpers/mod.rs @@ -0,0 +1,4 @@ +//! Test helpers for Cairo VM — enabled by the `function_runner` feature. + +pub mod error_utils; +pub mod test_utils; diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs new file mode 100644 index 0000000000..649cda3238 --- /dev/null +++ b/vm/src/test_helpers/test_utils.rs @@ -0,0 +1,53 @@ +/// Loads a compiled Cairo `.json` program from the same directory as the calling source file. +/// +/// Pass only the filename (no directory prefix). The directory is inferred from the call site +/// via `file!()`, so the `.json` must live next to the `.cairo` and `.rs` files. +/// +/// # Example +/// ```rust +/// static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("main_math_test.json")); +/// ``` +/// +/// # Panics +/// - If the `.json` file does not exist: run `make tests_cairo_programs` first. +/// - If the `.json` file cannot be parsed as a Cairo `Program`. +#[macro_export] +macro_rules! load_cairo_program { + ($name:literal) => {{ + // CARGO_MANIFEST_DIR is the `vm/` crate dir; workspace root is one level up. + // file!() expands at the call site — with_file_name replaces the filename portion + // so the JSON is resolved relative to the calling source file's directory. + let json_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("vm crate should have a parent directory") + .join(file!()) + .with_file_name($name); + + let bytes = std::fs::read(&json_path).unwrap_or_else(|err| { + panic!( + "Cairo program not found at {json_path:?}: {err}\n\ + Did you run `make cairo_test_suite_programs`?" + ) + }); + + $crate::types::program::Program::from_bytes(&bytes, None) + .unwrap_or_else(|e| panic!("Failed to parse Cairo program at {json_path:?}: {e}")) + }}; +} + +/// Asserts that a `MaybeRelocatable` reference equals a value convertible into `MaybeRelocatable`. +#[macro_export] +macro_rules! assert_mr_eq { + ($left:expr, $right:expr) => {{ + let right_mr = ($right) + .try_into() + .unwrap_or_else(|e| panic!("conversion to MaybeRelocatable failed: {e:?}")); + assert_eq!($left, &right_mr); + }}; + ($left:expr, $right:expr, $($arg:tt)+) => {{ + let right_mr = ($right) + .try_into() + .unwrap_or_else(|e| panic!("conversion to MaybeRelocatable failed: {e:?}")); + assert_eq!($left, &right_mr, $($arg)+); + }}; +} From 0edf8b8defe0ac1b72106dabae4896cf742e3c5a Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 15:54:33 +0300 Subject: [PATCH 02/31] test(test_helpers): add unit tests for assert_mr_eq!, load_cairo_program! and error_utils checkers Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + vm/src/test_helpers/dummy.json | 69 ++++++++ vm/src/test_helpers/error_utils.rs | 246 +++++++++++++++++++++++++++++ vm/src/test_helpers/test_utils.rs | 87 ++++++++++ 4 files changed, 403 insertions(+) create mode 100644 vm/src/test_helpers/dummy.json diff --git a/.gitignore b/.gitignore index 69fa82e403..8698bfa233 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ **/*.json !hint_accountant/whitelists/*.json !cairo_programs/manually_compiled/*.json +!vm/src/test_helpers/dummy.json **/*.casm **/*.sierra **/*.trace diff --git a/vm/src/test_helpers/dummy.json b/vm/src/test_helpers/dummy.json new file mode 100644 index 0000000000..4c1bf5ad81 --- /dev/null +++ b/vm/src/test_helpers/dummy.json @@ -0,0 +1,69 @@ +{ + "attributes": [], + "builtins": [], + "compiler_version": "0.13.5", + "data": [ + "0x208b7fff7fff7ffe" + ], + "debug_info": { + "file_contents": {}, + "instruction_locations": { + "0": { + "accessible_scopes": [ + "__main__", + "__main__.main" + ], + "flow_tracking_data": { + "ap_tracking": { + "group": 0, + "offset": 0 + }, + "reference_ids": {} + }, + "hints": [], + "inst": { + "end_col": 15, + "end_line": 2, + "input_file": { + "filename": "vm/src/test_helpers/dummy.cairo" + }, + "start_col": 5, + "start_line": 2 + } + } + } + }, + "hints": {}, + "identifiers": { + "__main__.main": { + "decorators": [], + "pc": 0, + "type": "function" + }, + "__main__.main.Args": { + "full_name": "__main__.main.Args", + "members": {}, + "size": 0, + "type": "struct" + }, + "__main__.main.ImplicitArgs": { + "full_name": "__main__.main.ImplicitArgs", + "members": {}, + "size": 0, + "type": "struct" + }, + "__main__.main.Return": { + "cairo_type": "()", + "type": "type_definition" + }, + "__main__.main.SIZEOF_LOCALS": { + "type": "const", + "value": 0 + } + }, + "main_scope": "__main__", + "prime": "0x800000000000011000000000000000000000000000000000000000000000001", + "reference_manager": { + "references": [] + } +} diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index 51cf3b5751..a4eabfbb18 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -168,3 +168,249 @@ pub fn expect_split_int_limb_out_of_range(res: &std::result::Result<(), CairoRun }) if matches!(boxed.as_ref(), (_, HintError::SplitIntLimbOutOfRange(_))) ); } + +#[cfg(test)] +mod tests { + use crate::{ + types::relocatable::{MaybeRelocatable, Relocatable}, + vm::errors::{ + cairo_run_errors::CairoRunError, hint_errors::HintError, + vm_errors::VirtualMachineError, vm_exception::VmException, + }, + }; + + use super::*; + + /// Wraps a `HintError` in the full `CairoRunError::VmException` chain expected by the checkers. + fn hint_err(hint_error: HintError) -> std::result::Result<(), CairoRunError> { + Err(CairoRunError::VmException(VmException { + pc: Relocatable::from((0, 0)), + inst_location: None, + inner_exc: VirtualMachineError::Hint(Box::new((0, hint_error))), + error_attr_value: None, + traceback: None, + })) + } + + /// `assert_vm_result!(ok)` does not panic on `Ok`. + #[test] + fn assert_vm_result_ok_passes() { + assert_vm_result!(Ok::<(), i32>(()), ok); + } + + /// `assert_vm_result!(err pat)` does not panic when the error matches the pattern. + #[test] + fn assert_vm_result_err_passes() { + assert_vm_result!(Err::<(), i32>(42), err 42); + } + + /// `assert_vm_result!(err pat if guard)` does not panic when both pattern and guard match. + #[test] + fn assert_vm_result_err_with_guard_passes() { + assert_vm_result!(Err::<(), i32>(42), err x if *x == 42); + } + + /// `expect_ok` does not panic on `Ok(())`. + #[test] + fn expect_ok_passes() { + expect_ok(&Ok(())); + } + + /// `expect_hint_assert_not_zero` does not panic on `HintError::AssertNotZero`. + #[test] + fn expect_hint_assert_not_zero_passes() { + let res = hint_err(HintError::AssertNotZero(Box::default())); + expect_hint_assert_not_zero(&res); + } + + /// `expect_assert_not_equal_fail` does not panic on `HintError::AssertNotEqualFail`. + #[test] + fn expect_assert_not_equal_fail_passes() { + let res = hint_err(HintError::AssertNotEqualFail(Box::new(( + MaybeRelocatable::from(0), + MaybeRelocatable::from(0), + )))); + expect_assert_not_equal_fail(&res); + } + + /// `expect_diff_type_comparison` does not panic on `VirtualMachineError::DiffTypeComparison`. + #[test] + fn expect_diff_type_comparison_passes() { + let res = hint_err(HintError::Internal(VirtualMachineError::DiffTypeComparison( + Box::new((MaybeRelocatable::from(0), MaybeRelocatable::from((0, 0)))), + ))); + expect_diff_type_comparison(&res); + } + + /// `expect_diff_index_comp` does not panic on `VirtualMachineError::DiffIndexComp`. + #[test] + fn expect_diff_index_comp_passes() { + let res = hint_err(HintError::Internal(VirtualMachineError::DiffIndexComp( + Box::new((Relocatable::from((0, 0)), Relocatable::from((1, 0)))), + ))); + expect_diff_index_comp(&res); + } + + /// `expect_hint_value_outside_250_bit_range` does not panic on `HintError::ValueOutside250BitRange`. + #[test] + fn expect_hint_value_outside_250_bit_range_passes() { + let res = hint_err(HintError::ValueOutside250BitRange(Box::default())); + expect_hint_value_outside_250_bit_range(&res); + } + + /// `expect_non_le_felt252` does not panic on `HintError::NonLeFelt252`. + #[test] + fn expect_non_le_felt252_passes() { + let res = hint_err(HintError::NonLeFelt252(Box::default())); + expect_non_le_felt252(&res); + } + + /// `expect_assert_lt_felt252` does not panic on `HintError::AssertLtFelt252`. + #[test] + fn expect_assert_lt_felt252_passes() { + let res = hint_err(HintError::AssertLtFelt252(Box::default())); + expect_assert_lt_felt252(&res); + } + + /// `expect_hint_value_outside_valid_range` does not panic on `HintError::ValueOutsideValidRange`. + #[test] + fn expect_hint_value_outside_valid_range_passes() { + let res = hint_err(HintError::ValueOutsideValidRange(Box::default())); + expect_hint_value_outside_valid_range(&res); + } + + /// `expect_hint_out_of_valid_range` does not panic on `HintError::OutOfValidRange`. + #[test] + fn expect_hint_out_of_valid_range_passes() { + let res = hint_err(HintError::OutOfValidRange(Box::default())); + expect_hint_out_of_valid_range(&res); + } + + /// `expect_split_int_not_zero` does not panic on `HintError::SplitIntNotZero`. + #[test] + fn expect_split_int_not_zero_passes() { + let res = hint_err(HintError::SplitIntNotZero); + expect_split_int_not_zero(&res); + } + + /// `expect_split_int_limb_out_of_range` does not panic on `HintError::SplitIntLimbOutOfRange`. + #[test] + fn expect_split_int_limb_out_of_range_passes() { + let res = hint_err(HintError::SplitIntLimbOutOfRange(Box::default())); + expect_split_int_limb_out_of_range(&res); + } + + // --- unhappy path: wrong error variant should panic --- + + /// `assert_vm_result!(ok)` panics when given `Err`. + #[test] + #[should_panic(expected = "Expected Ok, got Err")] + fn assert_vm_result_ok_panics_on_err() { + assert_vm_result!(Err::<(), i32>(42), ok); + } + + /// `assert_vm_result!(err pat)` panics when given `Ok`. + #[test] + #[should_panic(expected = "Expected Err, got Ok")] + fn assert_vm_result_err_panics_on_ok() { + assert_vm_result!(Ok::<(), i32>(()), err 42); + } + + /// `assert_vm_result!(err pat)` panics when the error doesn't match the pattern. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn assert_vm_result_err_panics_on_wrong_variant() { + assert_vm_result!(Err::<(), i32>(1), err 42); + } + + /// `assert_vm_result!(err pat if guard)` panics when the guard fails. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn assert_vm_result_err_with_guard_panics_on_failed_guard() { + assert_vm_result!(Err::<(), i32>(42), err x if *x == 0); + } + + /// `expect_ok` panics when given an `Err`. + #[test] + #[should_panic(expected = "Expected Ok, got Err")] + fn expect_ok_panics_on_err() { + expect_ok(&hint_err(HintError::SplitIntNotZero)); + } + + /// Each `expect_*` checker panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_hint_assert_not_zero_panics_on_wrong_variant() { + expect_hint_assert_not_zero(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_assert_not_equal_fail` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_assert_not_equal_fail_panics_on_wrong_variant() { + expect_assert_not_equal_fail(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_diff_type_comparison` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_diff_type_comparison_panics_on_wrong_variant() { + expect_diff_type_comparison(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_diff_index_comp` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_diff_index_comp_panics_on_wrong_variant() { + expect_diff_index_comp(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_hint_value_outside_250_bit_range` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_hint_value_outside_250_bit_range_panics_on_wrong_variant() { + expect_hint_value_outside_250_bit_range(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_non_le_felt252` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_non_le_felt252_panics_on_wrong_variant() { + expect_non_le_felt252(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_assert_lt_felt252` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_assert_lt_felt252_panics_on_wrong_variant() { + expect_assert_lt_felt252(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_hint_value_outside_valid_range` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_hint_value_outside_valid_range_panics_on_wrong_variant() { + expect_hint_value_outside_valid_range(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_hint_out_of_valid_range` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_hint_out_of_valid_range_panics_on_wrong_variant() { + expect_hint_out_of_valid_range(&hint_err(HintError::SplitIntNotZero)); + } + + /// `expect_split_int_not_zero` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_split_int_not_zero_panics_on_wrong_variant() { + expect_split_int_not_zero(&hint_err(HintError::SplitIntLimbOutOfRange(Box::default()))); + } + + /// `expect_split_int_limb_out_of_range` panics when given a different error variant. + #[test] + #[should_panic(expected = "Unexpected error variant")] + fn expect_split_int_limb_out_of_range_panics_on_wrong_variant() { + expect_split_int_limb_out_of_range(&hint_err(HintError::SplitIntNotZero)); + } +} diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs index 649cda3238..ed284e59a5 100644 --- a/vm/src/test_helpers/test_utils.rs +++ b/vm/src/test_helpers/test_utils.rs @@ -51,3 +51,90 @@ macro_rules! assert_mr_eq { assert_eq!($left, &right_mr, $($arg)+); }}; } + +#[cfg(test)] +mod tests { + use crate::types::relocatable::{MaybeRelocatable, Relocatable}; + + /// `load_cairo_program!` successfully loads a compiled Cairo program from the same directory. + /// + /// The source `dummy.cairo` used to produce `dummy.json` is: + /// ```cairo + /// func main() { + /// return (); + /// } + /// ``` + #[test] + fn load_cairo_program_loads_dummy() { + let program = load_cairo_program!("dummy.json"); + assert!(!program.shared_program_data.data.is_empty()); + } + + /// `load_cairo_program!` panics when the file does not exist. + #[test] + #[should_panic(expected = "Cairo program not found")] + fn load_cairo_program_panics_on_missing_file() { + load_cairo_program!("nonexistent.json"); + } + + /// `assert_mr_eq!` passes when an integer `MaybeRelocatable` equals the given felt value. + #[test] + fn assert_mr_eq_int_passes() { + let val = MaybeRelocatable::from(42); + assert_mr_eq!(&val, 42); + } + + /// `assert_mr_eq!` passes when a relocatable `MaybeRelocatable` equals the given pair. + #[test] + fn assert_mr_eq_relocatable_passes() { + let val = MaybeRelocatable::from(Relocatable::from((1, 5))); + assert_mr_eq!(&val, Relocatable::from((1, 5))); + } + + /// `assert_mr_eq!` passes with a custom message format. + #[test] + fn assert_mr_eq_with_message_passes() { + let val = MaybeRelocatable::from(7); + assert_mr_eq!(&val, 7, "value at index {} should be 7", 0); + } + + /// `assert_mr_eq!` panics when values differ. + #[test] + #[should_panic] + fn assert_mr_eq_panics_on_mismatch() { + let val = MaybeRelocatable::from(1); + assert_mr_eq!(&val, 2); + } + + /// `assert_mr_eq!` panics with a custom message when values differ. + #[test] + #[should_panic(expected = "wrong value")] + fn assert_mr_eq_with_message_panics_on_mismatch() { + let val = MaybeRelocatable::from(1); + assert_mr_eq!(&val, 2, "wrong value"); + } + + /// `assert_mr_eq!` panics when comparing a felt against a relocatable. + #[test] + #[should_panic] + fn assert_mr_eq_panics_felt_vs_relocatable() { + let val = MaybeRelocatable::from(1); + assert_mr_eq!(&val, Relocatable::from((0, 1))); + } + + /// `assert_mr_eq!` panics when relocatables have the same offset but different segments. + #[test] + #[should_panic] + fn assert_mr_eq_panics_relocatable_diff_segment() { + let val = MaybeRelocatable::from(Relocatable::from((0, 5))); + assert_mr_eq!(&val, Relocatable::from((1, 5))); + } + + /// `assert_mr_eq!` panics when relocatables have the same segment but different offsets. + #[test] + #[should_panic] + fn assert_mr_eq_panics_relocatable_diff_offset() { + let val = MaybeRelocatable::from(Relocatable::from((1, 0))); + assert_mr_eq!(&val, Relocatable::from((1, 1))); + } +} From 298b0c051f74867785fdaae45bf8fc1fa8ced127 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 16:13:16 +0300 Subject: [PATCH 03/31] style: cargo fmt Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/error_utils.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index a4eabfbb18..115f696461 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -236,9 +236,12 @@ mod tests { /// `expect_diff_type_comparison` does not panic on `VirtualMachineError::DiffTypeComparison`. #[test] fn expect_diff_type_comparison_passes() { - let res = hint_err(HintError::Internal(VirtualMachineError::DiffTypeComparison( - Box::new((MaybeRelocatable::from(0), MaybeRelocatable::from((0, 0)))), - ))); + let res = hint_err(HintError::Internal( + VirtualMachineError::DiffTypeComparison(Box::new(( + MaybeRelocatable::from(0), + MaybeRelocatable::from((0, 0)), + ))), + )); expect_diff_type_comparison(&res); } From 26c995c42235567426dd40ec82dfda0228615972 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 17:13:38 +0300 Subject: [PATCH 04/31] test: add conversion-failure and allow-large-err fixes to test_helpers - Add AlwaysFailConversion helper + 2 tests for assert_mr_eq! unwrap_or_else panic branch (no-message and message variants) - Allow clippy::result_large_err on hint_err test helper Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/error_utils.rs | 1 + vm/src/test_helpers/test_utils.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index 115f696461..e30c10a63a 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -182,6 +182,7 @@ mod tests { use super::*; /// Wraps a `HintError` in the full `CairoRunError::VmException` chain expected by the checkers. + #[allow(clippy::result_large_err)] fn hint_err(hint_error: HintError) -> std::result::Result<(), CairoRunError> { Err(CairoRunError::VmException(VmException { pc: Relocatable::from((0, 0)), diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs index ed284e59a5..df7d66106a 100644 --- a/vm/src/test_helpers/test_utils.rs +++ b/vm/src/test_helpers/test_utils.rs @@ -56,6 +56,17 @@ macro_rules! assert_mr_eq { mod tests { use crate::types::relocatable::{MaybeRelocatable, Relocatable}; + /// A type whose `TryInto` always fails, used to exercise + /// the `unwrap_or_else` panic branch in `assert_mr_eq!`. + struct AlwaysFailConversion; + + impl TryFrom for MaybeRelocatable { + type Error = &'static str; + fn try_from(_: AlwaysFailConversion) -> Result { + Err("intentional failure") + } + } + /// `load_cairo_program!` successfully loads a compiled Cairo program from the same directory. /// /// The source `dummy.cairo` used to produce `dummy.json` is: @@ -137,4 +148,20 @@ mod tests { let val = MaybeRelocatable::from(Relocatable::from((1, 0))); assert_mr_eq!(&val, Relocatable::from((1, 1))); } + + /// `assert_mr_eq!` (no-message variant) panics when `try_into` conversion fails. + #[test] + #[should_panic(expected = "conversion to MaybeRelocatable failed")] + fn assert_mr_eq_panics_on_conversion_failure() { + let val = MaybeRelocatable::from(42); + assert_mr_eq!(&val, AlwaysFailConversion); + } + + /// `assert_mr_eq!` (message variant) panics when `try_into` conversion fails. + #[test] + #[should_panic(expected = "conversion to MaybeRelocatable failed")] + fn assert_mr_eq_with_message_panics_on_conversion_failure() { + let val = MaybeRelocatable::from(42); + assert_mr_eq!(&val, AlwaysFailConversion, "should not reach assert_eq"); + } } From 39fcf0f5cf2361678590b92cefa6f961e3f7eb48 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 17:18:18 +0300 Subject: [PATCH 05/31] chore: update CHANGELOG for PR #2378 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f72857217c..797911c0fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. #### Upcoming Changes +* refactor: add `CairoFunctionRunner` type alias for `CairoRunner` under the `test_utils` feature flag [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) +* refactor: add `function_runner` feature flag and `CairoFunctionRunner` type alias for `CairoRunner` [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `function_runner` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) + * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) * Add union merge strategy for CHANGELOG.md [#2345](https://github.com/lambdaclass/cairo-vm/pull/2345) From b4cb481989c7a35748ab3d299ad21855eaf2a673 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 19:09:16 +0300 Subject: [PATCH 06/31] docs: mark load_cairo_program! example as ignore to suppress llvm-cov noise Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/test_utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs index df7d66106a..3e4c086a0e 100644 --- a/vm/src/test_helpers/test_utils.rs +++ b/vm/src/test_helpers/test_utils.rs @@ -4,7 +4,7 @@ /// via `file!()`, so the `.json` must live next to the `.cairo` and `.rs` files. /// /// # Example -/// ```rust +/// ```rust,ignore /// static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("main_math_test.json")); /// ``` /// From 2ccebe3aa13ded362b7f4e145aa4c9db12b53534 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 30 Mar 2026 11:42:49 +0300 Subject: [PATCH 07/31] fix: replace unwrap_or_else closures in macros to avoid llvm-cov empty function name error #[macro_export] macros containing closures (|x| ...) cause llvm-cov to emit a "function name is empty" error. Replaced unwrap_or_else(|e| panic!(...)) with match expressions to eliminate closures from macro expansions. Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/test_utils.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs index 3e4c086a0e..4c40f15a10 100644 --- a/vm/src/test_helpers/test_utils.rs +++ b/vm/src/test_helpers/test_utils.rs @@ -23,15 +23,18 @@ macro_rules! load_cairo_program { .join(file!()) .with_file_name($name); - let bytes = std::fs::read(&json_path).unwrap_or_else(|err| { - panic!( + let bytes = match std::fs::read(&json_path) { + Ok(b) => b, + Err(err) => panic!( "Cairo program not found at {json_path:?}: {err}\n\ Did you run `make cairo_test_suite_programs`?" - ) - }); + ), + }; - $crate::types::program::Program::from_bytes(&bytes, None) - .unwrap_or_else(|e| panic!("Failed to parse Cairo program at {json_path:?}: {e}")) + match $crate::types::program::Program::from_bytes(&bytes, None) { + Ok(p) => p, + Err(e) => panic!("Failed to parse Cairo program at {json_path:?}: {e}"), + } }}; } @@ -39,15 +42,17 @@ macro_rules! load_cairo_program { #[macro_export] macro_rules! assert_mr_eq { ($left:expr, $right:expr) => {{ - let right_mr = ($right) - .try_into() - .unwrap_or_else(|e| panic!("conversion to MaybeRelocatable failed: {e:?}")); + let right_mr = match ($right).try_into() { + Ok(v) => v, + Err(e) => panic!("conversion to MaybeRelocatable failed: {e:?}"), + }; assert_eq!($left, &right_mr); }}; ($left:expr, $right:expr, $($arg:tt)+) => {{ - let right_mr = ($right) - .try_into() - .unwrap_or_else(|e| panic!("conversion to MaybeRelocatable failed: {e:?}")); + let right_mr = match ($right).try_into() { + Ok(v) => v, + Err(e) => panic!("conversion to MaybeRelocatable failed: {e:?}"), + }; assert_eq!($left, &right_mr, $($arg)+); }}; } From 153f306d61628da8b4878debdffad3ef38d91860 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 12:47:54 +0300 Subject: [PATCH 08/31] =?UTF-8?q?refactor:=20update=20function=5Frunner=20?= =?UTF-8?q?=E2=86=92=20test=5Futils=20cfg=20gates=20and=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to dropping the function_runner feature flag. Gate test_helpers module and function_runner module under test_utils, and update the doc comment in function_runner.rs accordingly. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- vm/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 797911c0fe..28cf2b20cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. * refactor: add `CairoFunctionRunner` type alias for `CairoRunner` under the `test_utils` feature flag [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) * refactor: add `function_runner` feature flag and `CairoFunctionRunner` type alias for `CairoRunner` [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `function_runner` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 2406f05bf1..dd485104bc 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -26,7 +26,7 @@ pub mod types; pub mod utils; pub mod vm; -#[cfg(feature = "function_runner")] +#[cfg(feature = "test_utils")] pub mod test_helpers; // TODO: use `Felt` directly From 5e2dd1152d339d2336aa2d8a3a3dbcb8fcdd2fdc Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 23:20:45 +0300 Subject: [PATCH 09/31] refactor: make function_runner module and its methods public Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 3 --- vm/src/test_helpers/mod.rs | 4 ++-- vm/src/vm/runners/function_runner.rs | 5 ++--- vm/src/vm/runners/mod.rs | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cf2b20cb..a795769cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,6 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. --- #### Upcoming Changes - -* refactor: add `CairoFunctionRunner` type alias for `CairoRunner` under the `test_utils` feature flag [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) -* refactor: add `function_runner` feature flag and `CairoFunctionRunner` type alias for `CairoRunner` [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) diff --git a/vm/src/test_helpers/mod.rs b/vm/src/test_helpers/mod.rs index 438189d0ad..41bbbe577a 100644 --- a/vm/src/test_helpers/mod.rs +++ b/vm/src/test_helpers/mod.rs @@ -1,4 +1,4 @@ -//! Test helpers for Cairo VM — enabled by the `function_runner` feature. - +//! Test helpers for Cairo VM — enabled by the `test_utils` feature. +#[cfg(feature = "test_utils")] pub mod error_utils; pub mod test_utils; diff --git a/vm/src/vm/runners/function_runner.rs b/vm/src/vm/runners/function_runner.rs index 96f3b2ee67..9ac098091a 100644 --- a/vm/src/vm/runners/function_runner.rs +++ b/vm/src/vm/runners/function_runner.rs @@ -19,7 +19,6 @@ use crate::vm::runners::cairo_runner::{CairoArg, CairoRunner, ORDERED_BUILTIN_LI use crate::vm::security::verify_secure_runner; /// Identifies a Cairo function entrypoint either by function name or by program counter. -#[allow(dead_code)] pub enum EntryPoint<'a> { Name(&'a str), Pc(usize), @@ -75,7 +74,7 @@ impl CairoRunner { /// Resolves the entrypoint, builds the call stack, runs until the function's end PC, /// and optionally verifies security constraints. #[allow(clippy::result_large_err)] - pub(crate) fn run_from_entrypoint( + pub fn run_from_entrypoint( &mut self, entrypoint: EntryPoint<'_>, args: &[CairoArg], @@ -106,7 +105,7 @@ impl CairoRunner { /// Resolves `__main__.` to its PC, following alias chains. #[allow(clippy::result_large_err)] - pub(crate) fn get_function_pc(&self, entrypoint: &str) -> Result { + pub fn get_function_pc(&self, entrypoint: &str) -> Result { let full_name = format!("__main__.{entrypoint}"); let identifier = self .program diff --git a/vm/src/vm/runners/mod.rs b/vm/src/vm/runners/mod.rs index 1bad5f2627..03f61b9fa1 100644 --- a/vm/src/vm/runners/mod.rs +++ b/vm/src/vm/runners/mod.rs @@ -2,4 +2,4 @@ pub mod builtin_runner; pub mod cairo_pie; pub mod cairo_runner; #[cfg(feature = "test_utils")] -pub(crate) mod function_runner; +pub mod function_runner; From 516a2a57e3fe0d6685a1c748d45791349a27c400 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 23:47:13 +0300 Subject: [PATCH 10/31] chore: update CHANGELOG PR link from #2378 to #2381 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a795769cf8..0c26e7c2eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. --- #### Upcoming Changes -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) From 53a159fbcab060d9cd2f9ca47412c7ea3b141a28 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 9 Apr 2026 12:13:04 +0300 Subject: [PATCH 11/31] refactor: replace assert_mr_eq! macro with generic function Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/error_utils.rs | 367 ++++++++++------------------- vm/src/test_helpers/test_utils.rs | 145 +++++------- vm/src/vm/errors/hint_errors.rs | 3 + vm/src/vm/errors/vm_errors.rs | 3 + 4 files changed, 195 insertions(+), 323 deletions(-) diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index e30c10a63a..826cc2aaff 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -41,132 +41,91 @@ macro_rules! assert_vm_result { } /// Type alias for check functions that validate test results. -pub type VmCheck = fn(&std::result::Result); +pub type VmCheck = fn(&Result); /// Asserts that the result is `Ok`. -pub fn expect_ok(res: &std::result::Result<(), CairoRunError>) { +pub fn expect_ok(res: &Result<(), CairoRunError>) { assert_vm_result!(res, ok); } -/// Asserts that the result is `HintError::AssertNotZero`. -pub fn expect_hint_assert_not_zero(res: &std::result::Result<(), CairoRunError>) { +/// Asserts that the result is a `VirtualMachineError` satisfying `predicate`. +fn expect_vm_error( + res: &Result<(), CairoRunError>, + predicate: impl Fn(&VirtualMachineError) -> bool, +) { assert_vm_result!( res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::AssertNotZero(_))) + err CairoRunError::VmException(VmException { inner_exc, .. }) if predicate(inner_exc) ); } -/// Asserts that the result is `HintError::AssertNotEqualFail`. -pub fn expect_assert_not_equal_fail(res: &std::result::Result<(), CairoRunError>) { +/// Asserts that the result is a `HintError` satisfying `predicate`. +fn expect_hint_error( + res: &Result<(), CairoRunError>, + predicate: impl Fn(&HintError) -> bool, +) { assert_vm_result!( res, err CairoRunError::VmException(VmException { inner_exc: VirtualMachineError::Hint(boxed), .. - }) if matches!(boxed.as_ref(), (_, HintError::AssertNotEqualFail(_))) + }) if predicate(&boxed.as_ref().1) ); } -/// Asserts that the result is `HintError::Internal(VirtualMachineError::DiffTypeComparison)`. -pub fn expect_diff_type_comparison(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::Internal(VirtualMachineError::DiffTypeComparison(_)))) - ); +/// Asserts that the result is `HintError::AssertNotZero`. +pub fn expect_hint_assert_not_zero(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::AssertNotZero(_))); } -/// Asserts that the result is `HintError::Internal(VirtualMachineError::DiffIndexComp)`. -pub fn expect_diff_index_comp(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::Internal(VirtualMachineError::DiffIndexComp(_)))) - ); +/// Asserts that the result is `HintError::AssertNotEqualFail`. +pub fn expect_assert_not_equal_fail(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::AssertNotEqualFail(_))); +} + +/// Asserts that the result is `VirtualMachineError::DiffTypeComparison`. +pub fn expect_diff_type_comparison(res: &Result<(), CairoRunError>) { + expect_vm_error(res, |e| matches!(e, VirtualMachineError::DiffTypeComparison(_))); +} + +/// Asserts that the result is `VirtualMachineError::DiffIndexComp`. +pub fn expect_diff_index_comp(res: &Result<(), CairoRunError>) { + expect_vm_error(res, |e| matches!(e, VirtualMachineError::DiffIndexComp(_))); } /// Asserts that the result is `HintError::ValueOutside250BitRange`. -pub fn expect_hint_value_outside_250_bit_range(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::ValueOutside250BitRange(_))) - ); +pub fn expect_hint_value_outside_250_bit_range(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::ValueOutside250BitRange(_))); } /// Asserts that the result is `HintError::NonLeFelt252`. -pub fn expect_non_le_felt252(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::NonLeFelt252(_))) - ); +pub fn expect_non_le_felt252(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::NonLeFelt252(_))); } /// Asserts that the result is `HintError::AssertLtFelt252`. -pub fn expect_assert_lt_felt252(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::AssertLtFelt252(_))) - ); +pub fn expect_assert_lt_felt252(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::AssertLtFelt252(_))); } /// Asserts that the result is `HintError::ValueOutsideValidRange`. -pub fn expect_hint_value_outside_valid_range(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::ValueOutsideValidRange(_))) - ); +pub fn expect_hint_value_outside_valid_range(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::ValueOutsideValidRange(_))); } /// Asserts that the result is `HintError::OutOfValidRange`. -pub fn expect_hint_out_of_valid_range(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::OutOfValidRange(_))) - ); +pub fn expect_hint_out_of_valid_range(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::OutOfValidRange(_))); } /// Asserts that the result is `HintError::SplitIntNotZero`. -pub fn expect_split_int_not_zero(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::SplitIntNotZero)) - ); +pub fn expect_split_int_not_zero(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::SplitIntNotZero)); } /// Asserts that the result is `HintError::SplitIntLimbOutOfRange`. -pub fn expect_split_int_limb_out_of_range(res: &std::result::Result<(), CairoRunError>) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { - inner_exc: VirtualMachineError::Hint(boxed), - .. - }) if matches!(boxed.as_ref(), (_, HintError::SplitIntLimbOutOfRange(_))) - ); +pub fn expect_split_int_limb_out_of_range(res: &Result<(), CairoRunError>) { + expect_hint_error(res, |e| matches!(e, HintError::SplitIntLimbOutOfRange(_))); } #[cfg(test)] @@ -178,14 +137,15 @@ mod tests { vm_errors::VirtualMachineError, vm_exception::VmException, }, }; + use rstest::rstest; use super::*; /// Wraps a `HintError` in the full `CairoRunError::VmException` chain expected by the checkers. #[allow(clippy::result_large_err)] - fn hint_err(hint_error: HintError) -> std::result::Result<(), CairoRunError> { + fn hint_err(hint_error: HintError) -> Result<(), CairoRunError> { Err(CairoRunError::VmException(VmException { - pc: Relocatable::from((0, 0)), + pc: Relocatable::default(), inst_location: None, inner_exc: VirtualMachineError::Hint(Box::new((0, hint_error))), error_attr_value: None, @@ -193,6 +153,18 @@ mod tests { })) } + /// Wraps a `VirtualMachineError` in `CairoRunError::VmException` directly. + #[allow(clippy::result_large_err)] + fn vm_err(vm_error: VirtualMachineError) -> Result<(), CairoRunError> { + Err(CairoRunError::VmException(VmException { + pc: Relocatable::default(), + inst_location: None, + inner_exc: vm_error, + error_attr_value: None, + traceback: None, + })) + } + /// `assert_vm_result!(ok)` does not panic on `Ok`. #[test] fn assert_vm_result_ok_passes() { @@ -217,94 +189,55 @@ mod tests { expect_ok(&Ok(())); } - /// `expect_hint_assert_not_zero` does not panic on `HintError::AssertNotZero`. - #[test] - fn expect_hint_assert_not_zero_passes() { - let res = hint_err(HintError::AssertNotZero(Box::default())); - expect_hint_assert_not_zero(&res); - } + // --- happy path: each checker passes on its correct error variant --- - /// `expect_assert_not_equal_fail` does not panic on `HintError::AssertNotEqualFail`. - #[test] - fn expect_assert_not_equal_fail_passes() { - let res = hint_err(HintError::AssertNotEqualFail(Box::new(( + #[rstest] + #[case::hint_assert_not_zero( + expect_hint_assert_not_zero, + hint_err(HintError::AssertNotZero(Box::default())) + )] + #[case::assert_not_equal_fail( + expect_assert_not_equal_fail, + hint_err(HintError::AssertNotEqualFail(Box::new(( MaybeRelocatable::from(0), MaybeRelocatable::from(0), - )))); - expect_assert_not_equal_fail(&res); - } - - /// `expect_diff_type_comparison` does not panic on `VirtualMachineError::DiffTypeComparison`. - #[test] - fn expect_diff_type_comparison_passes() { - let res = hint_err(HintError::Internal( - VirtualMachineError::DiffTypeComparison(Box::new(( - MaybeRelocatable::from(0), - MaybeRelocatable::from((0, 0)), - ))), - )); - expect_diff_type_comparison(&res); - } - - /// `expect_diff_index_comp` does not panic on `VirtualMachineError::DiffIndexComp`. - #[test] - fn expect_diff_index_comp_passes() { - let res = hint_err(HintError::Internal(VirtualMachineError::DiffIndexComp( - Box::new((Relocatable::from((0, 0)), Relocatable::from((1, 0)))), - ))); - expect_diff_index_comp(&res); - } - - /// `expect_hint_value_outside_250_bit_range` does not panic on `HintError::ValueOutside250BitRange`. - #[test] - fn expect_hint_value_outside_250_bit_range_passes() { - let res = hint_err(HintError::ValueOutside250BitRange(Box::default())); - expect_hint_value_outside_250_bit_range(&res); - } - - /// `expect_non_le_felt252` does not panic on `HintError::NonLeFelt252`. - #[test] - fn expect_non_le_felt252_passes() { - let res = hint_err(HintError::NonLeFelt252(Box::default())); - expect_non_le_felt252(&res); - } - - /// `expect_assert_lt_felt252` does not panic on `HintError::AssertLtFelt252`. - #[test] - fn expect_assert_lt_felt252_passes() { - let res = hint_err(HintError::AssertLtFelt252(Box::default())); - expect_assert_lt_felt252(&res); - } - - /// `expect_hint_value_outside_valid_range` does not panic on `HintError::ValueOutsideValidRange`. - #[test] - fn expect_hint_value_outside_valid_range_passes() { - let res = hint_err(HintError::ValueOutsideValidRange(Box::default())); - expect_hint_value_outside_valid_range(&res); - } - - /// `expect_hint_out_of_valid_range` does not panic on `HintError::OutOfValidRange`. - #[test] - fn expect_hint_out_of_valid_range_passes() { - let res = hint_err(HintError::OutOfValidRange(Box::default())); - expect_hint_out_of_valid_range(&res); - } - - /// `expect_split_int_not_zero` does not panic on `HintError::SplitIntNotZero`. - #[test] - fn expect_split_int_not_zero_passes() { - let res = hint_err(HintError::SplitIntNotZero); - expect_split_int_not_zero(&res); - } - - /// `expect_split_int_limb_out_of_range` does not panic on `HintError::SplitIntLimbOutOfRange`. - #[test] - fn expect_split_int_limb_out_of_range_passes() { - let res = hint_err(HintError::SplitIntLimbOutOfRange(Box::default())); - expect_split_int_limb_out_of_range(&res); - } - - // --- unhappy path: wrong error variant should panic --- + )))) + )] + #[case::diff_type_comparison( + expect_diff_type_comparison, + vm_err(VirtualMachineError::DiffTypeComparison(Box::new(( + MaybeRelocatable::from(0), + MaybeRelocatable::from((0, 0)), + )))) + )] + #[case::diff_index_comp(expect_diff_index_comp, vm_err(VirtualMachineError::DiffIndexComp(Box::default())))] + #[case::hint_value_outside_250_bit_range( + expect_hint_value_outside_250_bit_range, + hint_err(HintError::ValueOutside250BitRange(Box::default())) + )] + #[case::non_le_felt252(expect_non_le_felt252, hint_err(HintError::NonLeFelt252(Box::default())))] + #[case::assert_lt_felt252(expect_assert_lt_felt252, hint_err(HintError::AssertLtFelt252(Box::default())))] + #[case::hint_value_outside_valid_range( + expect_hint_value_outside_valid_range, + hint_err(HintError::ValueOutsideValidRange(Box::default())) + )] + #[case::hint_out_of_valid_range( + expect_hint_out_of_valid_range, + hint_err(HintError::OutOfValidRange(Box::default())) + )] + #[case::split_int_not_zero(expect_split_int_not_zero, hint_err(HintError::SplitIntNotZero))] + #[case::split_int_limb_out_of_range( + expect_split_int_limb_out_of_range, + hint_err(HintError::SplitIntLimbOutOfRange(Box::default())) + )] + fn checker_passes_on_correct_variant( + #[case] checker: VmCheck<()>, + #[case] res: Result<(), CairoRunError>, + ) { + checker(&res); + } + + // --- unhappy path: macro edge cases --- /// `assert_vm_result!(ok)` panics when given `Err`. #[test] @@ -338,83 +271,31 @@ mod tests { #[test] #[should_panic(expected = "Expected Ok, got Err")] fn expect_ok_panics_on_err() { - expect_ok(&hint_err(HintError::SplitIntNotZero)); + expect_ok(&hint_err(HintError::Dummy)); } - /// Each `expect_*` checker panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_hint_assert_not_zero_panics_on_wrong_variant() { - expect_hint_assert_not_zero(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_assert_not_equal_fail` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_assert_not_equal_fail_panics_on_wrong_variant() { - expect_assert_not_equal_fail(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_diff_type_comparison` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_diff_type_comparison_panics_on_wrong_variant() { - expect_diff_type_comparison(&hint_err(HintError::SplitIntNotZero)); - } + // --- unhappy path: each checker panics on a wrong error variant --- - /// `expect_diff_index_comp` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_diff_index_comp_panics_on_wrong_variant() { - expect_diff_index_comp(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_hint_value_outside_250_bit_range` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_hint_value_outside_250_bit_range_panics_on_wrong_variant() { - expect_hint_value_outside_250_bit_range(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_non_le_felt252` panics when given a different error variant. - #[test] + #[rstest] + #[case::hint_assert_not_zero(expect_hint_assert_not_zero)] + #[case::assert_not_equal_fail(expect_assert_not_equal_fail)] + #[case::hint_value_outside_250_bit_range(expect_hint_value_outside_250_bit_range)] + #[case::non_le_felt252(expect_non_le_felt252)] + #[case::assert_lt_felt252(expect_assert_lt_felt252)] + #[case::hint_value_outside_valid_range(expect_hint_value_outside_valid_range)] + #[case::hint_out_of_valid_range(expect_hint_out_of_valid_range)] + #[case::split_int_not_zero(expect_split_int_not_zero)] + #[case::split_int_limb_out_of_range(expect_split_int_limb_out_of_range)] #[should_panic(expected = "Unexpected error variant")] - fn expect_non_le_felt252_panics_on_wrong_variant() { - expect_non_le_felt252(&hint_err(HintError::SplitIntNotZero)); + fn hint_checker_panics_on_dummy_hint_error(#[case] checker: VmCheck<()>) { + checker(&hint_err(HintError::Dummy)); } - /// `expect_assert_lt_felt252` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_assert_lt_felt252_panics_on_wrong_variant() { - expect_assert_lt_felt252(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_hint_value_outside_valid_range` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_hint_value_outside_valid_range_panics_on_wrong_variant() { - expect_hint_value_outside_valid_range(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_hint_out_of_valid_range` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_hint_out_of_valid_range_panics_on_wrong_variant() { - expect_hint_out_of_valid_range(&hint_err(HintError::SplitIntNotZero)); - } - - /// `expect_split_int_not_zero` panics when given a different error variant. - #[test] - #[should_panic(expected = "Unexpected error variant")] - fn expect_split_int_not_zero_panics_on_wrong_variant() { - expect_split_int_not_zero(&hint_err(HintError::SplitIntLimbOutOfRange(Box::default()))); - } - - /// `expect_split_int_limb_out_of_range` panics when given a different error variant. - #[test] + #[rstest] + #[case::diff_type_comparison(expect_diff_type_comparison)] + #[case::diff_index_comp(expect_diff_index_comp)] #[should_panic(expected = "Unexpected error variant")] - fn expect_split_int_limb_out_of_range_panics_on_wrong_variant() { - expect_split_int_limb_out_of_range(&hint_err(HintError::SplitIntNotZero)); + fn vm_checker_panics_on_dummy_vm_error(#[case] checker: VmCheck<()>) { + checker(&vm_err(VirtualMachineError::Dummy)); } } diff --git a/vm/src/test_helpers/test_utils.rs b/vm/src/test_helpers/test_utils.rs index 4c40f15a10..ee83c1cb1d 100644 --- a/vm/src/test_helpers/test_utils.rs +++ b/vm/src/test_helpers/test_utils.rs @@ -1,3 +1,5 @@ +use crate::types::relocatable::MaybeRelocatable; + /// Loads a compiled Cairo `.json` program from the same directory as the calling source file. /// /// Pass only the filename (no directory prefix). The directory is inferred from the call site @@ -38,31 +40,37 @@ macro_rules! load_cairo_program { }}; } -/// Asserts that a `MaybeRelocatable` reference equals a value convertible into `MaybeRelocatable`. -#[macro_export] -macro_rules! assert_mr_eq { - ($left:expr, $right:expr) => {{ - let right_mr = match ($right).try_into() { - Ok(v) => v, - Err(e) => panic!("conversion to MaybeRelocatable failed: {e:?}"), - }; - assert_eq!($left, &right_mr); - }}; - ($left:expr, $right:expr, $($arg:tt)+) => {{ - let right_mr = match ($right).try_into() { - Ok(v) => v, - Err(e) => panic!("conversion to MaybeRelocatable failed: {e:?}"), - }; - assert_eq!($left, &right_mr, $($arg)+); - }}; +/// Asserts that two values are equal after converting both to [`MaybeRelocatable`]. +/// +/// If the left conversion fails, the panic message says "left conversion … failed". +/// If the right conversion fails, it says "right conversion … failed". +#[track_caller] +pub fn assert_mr_eq(left: L, right: R) +where + L: TryInto, + L::Error: core::fmt::Debug, + R: TryInto, + R::Error: core::fmt::Debug, +{ + let left_mr = match left.try_into() { + Ok(v) => v, + Err(e) => panic!("left conversion to MaybeRelocatable failed: {e:?}"), + }; + let right_mr = match right.try_into() { + Ok(v) => v, + Err(e) => panic!("right conversion to MaybeRelocatable failed: {e:?}"), + }; + assert_eq!(left_mr, right_mr); } #[cfg(test)] mod tests { + use super::assert_mr_eq; use crate::types::relocatable::{MaybeRelocatable, Relocatable}; + use rstest::rstest; /// A type whose `TryInto` always fails, used to exercise - /// the `unwrap_or_else` panic branch in `assert_mr_eq!`. + /// the conversion-failure panic branch in `assert_mr_eq`. struct AlwaysFailConversion; impl TryFrom for MaybeRelocatable { @@ -93,80 +101,57 @@ mod tests { load_cairo_program!("nonexistent.json"); } - /// `assert_mr_eq!` passes when an integer `MaybeRelocatable` equals the given felt value. - #[test] - fn assert_mr_eq_int_passes() { - let val = MaybeRelocatable::from(42); - assert_mr_eq!(&val, 42); - } + // --- assert_mr_eq: passing cases --- - /// `assert_mr_eq!` passes when a relocatable `MaybeRelocatable` equals the given pair. - #[test] - fn assert_mr_eq_relocatable_passes() { - let val = MaybeRelocatable::from(Relocatable::from((1, 5))); - assert_mr_eq!(&val, Relocatable::from((1, 5))); + #[rstest] + #[case::int(MaybeRelocatable::from(42), MaybeRelocatable::from(42))] + #[case::relocatable( + MaybeRelocatable::from(Relocatable::from((1, 5))), + MaybeRelocatable::from(Relocatable::from((1, 5))) + )] + fn assert_mr_eq_passes(#[case] left: MaybeRelocatable, #[case] right: MaybeRelocatable) { + assert_mr_eq(left, right); } - /// `assert_mr_eq!` passes with a custom message format. - #[test] - fn assert_mr_eq_with_message_passes() { - let val = MaybeRelocatable::from(7); - assert_mr_eq!(&val, 7, "value at index {} should be 7", 0); - } - - /// `assert_mr_eq!` panics when values differ. - #[test] + // --- assert_mr_eq: mismatch panics --- + + #[rstest] + #[case::int_mismatch(MaybeRelocatable::from(1), MaybeRelocatable::from(2))] + #[case::felt_vs_relocatable( + MaybeRelocatable::from(1), + MaybeRelocatable::from(Relocatable::from((0, 1))) + )] + #[case::relocatable_diff_segment( + MaybeRelocatable::from(Relocatable::from((0, 5))), + MaybeRelocatable::from(Relocatable::from((1, 5))) + )] + #[case::relocatable_diff_offset( + MaybeRelocatable::from(Relocatable::from((1, 0))), + MaybeRelocatable::from(Relocatable::from((1, 1))) + )] #[should_panic] - fn assert_mr_eq_panics_on_mismatch() { - let val = MaybeRelocatable::from(1); - assert_mr_eq!(&val, 2); + fn assert_mr_eq_panics_on_mismatch( + #[case] left: MaybeRelocatable, + #[case] right: MaybeRelocatable, + ) { + assert_mr_eq(left, right); } - /// `assert_mr_eq!` panics with a custom message when values differ. - #[test] - #[should_panic(expected = "wrong value")] - fn assert_mr_eq_with_message_panics_on_mismatch() { - let val = MaybeRelocatable::from(1); - assert_mr_eq!(&val, 2, "wrong value"); - } - - /// `assert_mr_eq!` panics when comparing a felt against a relocatable. - #[test] - #[should_panic] - fn assert_mr_eq_panics_felt_vs_relocatable() { - let val = MaybeRelocatable::from(1); - assert_mr_eq!(&val, Relocatable::from((0, 1))); - } - - /// `assert_mr_eq!` panics when relocatables have the same offset but different segments. - #[test] - #[should_panic] - fn assert_mr_eq_panics_relocatable_diff_segment() { - let val = MaybeRelocatable::from(Relocatable::from((0, 5))); - assert_mr_eq!(&val, Relocatable::from((1, 5))); - } - - /// `assert_mr_eq!` panics when relocatables have the same segment but different offsets. - #[test] - #[should_panic] - fn assert_mr_eq_panics_relocatable_diff_offset() { - let val = MaybeRelocatable::from(Relocatable::from((1, 0))); - assert_mr_eq!(&val, Relocatable::from((1, 1))); - } + // --- assert_mr_eq: conversion failure panics --- - /// `assert_mr_eq!` (no-message variant) panics when `try_into` conversion fails. + /// Panics with "right conversion" message when right `try_into` fails. #[test] - #[should_panic(expected = "conversion to MaybeRelocatable failed")] - fn assert_mr_eq_panics_on_conversion_failure() { + #[should_panic(expected = "right conversion to MaybeRelocatable failed")] + fn assert_mr_eq_panics_on_right_conversion_failure() { let val = MaybeRelocatable::from(42); - assert_mr_eq!(&val, AlwaysFailConversion); + assert_mr_eq(&val, AlwaysFailConversion); } - /// `assert_mr_eq!` (message variant) panics when `try_into` conversion fails. + /// Panics with "left conversion" message when left `try_into` fails. #[test] - #[should_panic(expected = "conversion to MaybeRelocatable failed")] - fn assert_mr_eq_with_message_panics_on_conversion_failure() { + #[should_panic(expected = "left conversion to MaybeRelocatable failed")] + fn assert_mr_eq_panics_on_left_conversion_failure() { let val = MaybeRelocatable::from(42); - assert_mr_eq!(&val, AlwaysFailConversion, "should not reach assert_eq"); + assert_mr_eq(AlwaysFailConversion, &val); } } diff --git a/vm/src/vm/errors/hint_errors.rs b/vm/src/vm/errors/hint_errors.rs index 5dc4a60357..6d8eab1831 100644 --- a/vm/src/vm/errors/hint_errors.rs +++ b/vm/src/vm/errors/hint_errors.rs @@ -196,6 +196,9 @@ pub enum HintError { CircuitEvaluationFailed(Box), #[error("high limb magnitude should be smaller than 2 ** 127: {0}")] BlsSplitError(Box), + #[cfg(feature = "test_utils")] + #[error("dummy hint error for testing")] + Dummy, } #[cfg(test)] diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index ddbc4cc109..38403a4e0a 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -141,6 +141,9 @@ pub enum VirtualMachineError { InvalidBlake2sFlags(u128), #[error("QM31 add mul opcode invalid flags {0}")] InvalidQM31AddMulFlags(u128), + #[cfg(feature = "test_utils")] + #[error("dummy vm error for testing")] + Dummy, } #[cfg(test)] From 3e693fbf3d7d6e9bc682f93a89b4140236950e5e Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 9 Apr 2026 12:23:32 +0300 Subject: [PATCH 12/31] chore: apply cargo fmt Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/error_utils.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index 826cc2aaff..b5943f12ad 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -60,10 +60,7 @@ fn expect_vm_error( } /// Asserts that the result is a `HintError` satisfying `predicate`. -fn expect_hint_error( - res: &Result<(), CairoRunError>, - predicate: impl Fn(&HintError) -> bool, -) { +fn expect_hint_error(res: &Result<(), CairoRunError>, predicate: impl Fn(&HintError) -> bool) { assert_vm_result!( res, err CairoRunError::VmException(VmException { @@ -85,7 +82,9 @@ pub fn expect_assert_not_equal_fail(res: &Result<(), CairoRunError>) { /// Asserts that the result is `VirtualMachineError::DiffTypeComparison`. pub fn expect_diff_type_comparison(res: &Result<(), CairoRunError>) { - expect_vm_error(res, |e| matches!(e, VirtualMachineError::DiffTypeComparison(_))); + expect_vm_error(res, |e| { + matches!(e, VirtualMachineError::DiffTypeComparison(_)) + }); } /// Asserts that the result is `VirtualMachineError::DiffIndexComp`. @@ -210,13 +209,22 @@ mod tests { MaybeRelocatable::from((0, 0)), )))) )] - #[case::diff_index_comp(expect_diff_index_comp, vm_err(VirtualMachineError::DiffIndexComp(Box::default())))] + #[case::diff_index_comp( + expect_diff_index_comp, + vm_err(VirtualMachineError::DiffIndexComp(Box::default())) + )] #[case::hint_value_outside_250_bit_range( expect_hint_value_outside_250_bit_range, hint_err(HintError::ValueOutside250BitRange(Box::default())) )] - #[case::non_le_felt252(expect_non_le_felt252, hint_err(HintError::NonLeFelt252(Box::default())))] - #[case::assert_lt_felt252(expect_assert_lt_felt252, hint_err(HintError::AssertLtFelt252(Box::default())))] + #[case::non_le_felt252( + expect_non_le_felt252, + hint_err(HintError::NonLeFelt252(Box::default())) + )] + #[case::assert_lt_felt252( + expect_assert_lt_felt252, + hint_err(HintError::AssertLtFelt252(Box::default())) + )] #[case::hint_value_outside_valid_range( expect_hint_value_outside_valid_range, hint_err(HintError::ValueOutsideValidRange(Box::default())) From 9b1b8f53337ca0e005883d2b6ef1668d2a0ea37c Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 9 Apr 2026 12:39:23 +0300 Subject: [PATCH 13/31] fix: expect_diff_type_comparison and expect_diff_index_comp unwrap Hint::Internal These errors arrive wrapped as Hint(Internal(...)) since they originate inside hint execution, not as bare VirtualMachineError variants. Remove now-unused expect_vm_error helper and vm_err test helper. Co-Authored-By: Claude Sonnet 4.6 --- vm/src/test_helpers/error_utils.rs | 57 ++++++++++-------------------- vm/src/vm/errors/vm_errors.rs | 3 -- 2 files changed, 18 insertions(+), 42 deletions(-) diff --git a/vm/src/test_helpers/error_utils.rs b/vm/src/test_helpers/error_utils.rs index b5943f12ad..82299e453f 100644 --- a/vm/src/test_helpers/error_utils.rs +++ b/vm/src/test_helpers/error_utils.rs @@ -48,17 +48,6 @@ pub fn expect_ok(res: &Result<(), CairoRunError>) { assert_vm_result!(res, ok); } -/// Asserts that the result is a `VirtualMachineError` satisfying `predicate`. -fn expect_vm_error( - res: &Result<(), CairoRunError>, - predicate: impl Fn(&VirtualMachineError) -> bool, -) { - assert_vm_result!( - res, - err CairoRunError::VmException(VmException { inner_exc, .. }) if predicate(inner_exc) - ); -} - /// Asserts that the result is a `HintError` satisfying `predicate`. fn expect_hint_error(res: &Result<(), CairoRunError>, predicate: impl Fn(&HintError) -> bool) { assert_vm_result!( @@ -80,16 +69,24 @@ pub fn expect_assert_not_equal_fail(res: &Result<(), CairoRunError>) { expect_hint_error(res, |e| matches!(e, HintError::AssertNotEqualFail(_))); } -/// Asserts that the result is `VirtualMachineError::DiffTypeComparison`. +/// Asserts that the result is `VirtualMachineError::DiffTypeComparison` wrapped in a hint. pub fn expect_diff_type_comparison(res: &Result<(), CairoRunError>) { - expect_vm_error(res, |e| { - matches!(e, VirtualMachineError::DiffTypeComparison(_)) + expect_hint_error(res, |e| { + matches!( + e, + HintError::Internal(VirtualMachineError::DiffTypeComparison(_)) + ) }); } -/// Asserts that the result is `VirtualMachineError::DiffIndexComp`. +/// Asserts that the result is `VirtualMachineError::DiffIndexComp` wrapped in a hint. pub fn expect_diff_index_comp(res: &Result<(), CairoRunError>) { - expect_vm_error(res, |e| matches!(e, VirtualMachineError::DiffIndexComp(_))); + expect_hint_error(res, |e| { + matches!( + e, + HintError::Internal(VirtualMachineError::DiffIndexComp(_)) + ) + }); } /// Asserts that the result is `HintError::ValueOutside250BitRange`. @@ -152,18 +149,6 @@ mod tests { })) } - /// Wraps a `VirtualMachineError` in `CairoRunError::VmException` directly. - #[allow(clippy::result_large_err)] - fn vm_err(vm_error: VirtualMachineError) -> Result<(), CairoRunError> { - Err(CairoRunError::VmException(VmException { - pc: Relocatable::default(), - inst_location: None, - inner_exc: vm_error, - error_attr_value: None, - traceback: None, - })) - } - /// `assert_vm_result!(ok)` does not panic on `Ok`. #[test] fn assert_vm_result_ok_passes() { @@ -204,14 +189,14 @@ mod tests { )] #[case::diff_type_comparison( expect_diff_type_comparison, - vm_err(VirtualMachineError::DiffTypeComparison(Box::new(( + hint_err(HintError::Internal(VirtualMachineError::DiffTypeComparison(Box::new(( MaybeRelocatable::from(0), MaybeRelocatable::from((0, 0)), - )))) + ))))) )] #[case::diff_index_comp( expect_diff_index_comp, - vm_err(VirtualMachineError::DiffIndexComp(Box::default())) + hint_err(HintError::Internal(VirtualMachineError::DiffIndexComp(Box::default()))) )] #[case::hint_value_outside_250_bit_range( expect_hint_value_outside_250_bit_range, @@ -287,6 +272,8 @@ mod tests { #[rstest] #[case::hint_assert_not_zero(expect_hint_assert_not_zero)] #[case::assert_not_equal_fail(expect_assert_not_equal_fail)] + #[case::diff_type_comparison(expect_diff_type_comparison)] + #[case::diff_index_comp(expect_diff_index_comp)] #[case::hint_value_outside_250_bit_range(expect_hint_value_outside_250_bit_range)] #[case::non_le_felt252(expect_non_le_felt252)] #[case::assert_lt_felt252(expect_assert_lt_felt252)] @@ -298,12 +285,4 @@ mod tests { fn hint_checker_panics_on_dummy_hint_error(#[case] checker: VmCheck<()>) { checker(&hint_err(HintError::Dummy)); } - - #[rstest] - #[case::diff_type_comparison(expect_diff_type_comparison)] - #[case::diff_index_comp(expect_diff_index_comp)] - #[should_panic(expected = "Unexpected error variant")] - fn vm_checker_panics_on_dummy_vm_error(#[case] checker: VmCheck<()>) { - checker(&vm_err(VirtualMachineError::Dummy)); - } } diff --git a/vm/src/vm/errors/vm_errors.rs b/vm/src/vm/errors/vm_errors.rs index 38403a4e0a..ddbc4cc109 100644 --- a/vm/src/vm/errors/vm_errors.rs +++ b/vm/src/vm/errors/vm_errors.rs @@ -141,9 +141,6 @@ pub enum VirtualMachineError { InvalidBlake2sFlags(u128), #[error("QM31 add mul opcode invalid flags {0}")] InvalidQM31AddMulFlags(u128), - #[cfg(feature = "test_utils")] - #[error("dummy vm error for testing")] - Dummy, } #[cfg(test)] From c4a8e042acc751bed3cb4df1b30580fb0469de8b Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 13:59:58 +0300 Subject: [PATCH 14/31] feat(makefile,ci): add cairo_test_suite_programs target and CI integration Makefile: - Add CAIRO_TEST_SUITE_ROOT, CAIRO_TEST_SUITE_FILES, COMPILED_CAIRO_TEST_SUITE variables to track cairo test suite sources - Add pattern rule to compile each .cairo to .json via cairo-compile - Add cairo_test_suite_programs target - Hook cairo_test_suite_programs into the test target - Add cairo_test_suite_programs to .PHONY CI (rust.yml): - Add cairo_test_suite_programs to the build-programs matrix - Add vm/src/tests/cairo_test_suite/**/*.json to CAIRO_PROGRAMS_PATH cache - Update all program cache keys to hash test suite source files - Add function_runner feature flag to the tests job Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/rust.yml | 40 +++++++++++++++++++++++--------------- Makefile | 13 +++++++++++-- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ade377346e..52210d6d11 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -18,6 +18,7 @@ env: cairo_programs/**/*.json !cairo_programs/manually_compiled/* cairo_programs/cairo-1-programs/bitwise.sierra + vm/src/tests/cairo_test_suite/**/*.json TEST_COLLECT_COVERAGE: 1 PROPTEST_CASES: 100 @@ -46,6 +47,7 @@ jobs: - cairo_bench_programs - cairo_proof_programs - cairo_test_programs + - cairo_test_suite_programs - cairo_1_test_contracts - cairo_2_test_contracts name: Build Cairo programs @@ -66,7 +68,7 @@ jobs: id: cache-programs with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} # This is not pretty, but we need `make` to see the compiled programs are # actually newer than the sources, otherwise it will try to rebuild them @@ -120,37 +122,43 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_test_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_test_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Fetch proof programs uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Fetch bench programs uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_bench_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_bench_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} + fail-on-cache-miss: true + - name: Fetch cairo test suite programs + uses: actions/cache/restore@v3 + with: + path: ${{ env.CAIRO_PROGRAMS_PATH }} + key: cairo_test_suite_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Fetch test contracts (Cairo 1) uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_1_test_contracts-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_1_test_contracts-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Fetch test contracts (Cairo 2) uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_2_test_contracts-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_2_test_contracts-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Merge caches uses: actions/cache/save@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} lint: needs: merge-caches @@ -179,7 +187,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Run clippy @@ -229,7 +237,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true # NOTE: we do this separately because --workspace operates in weird ways @@ -274,7 +282,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Check all features (workspace) @@ -309,7 +317,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: all-programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Install testing tools @@ -329,7 +337,7 @@ jobs: export PARTITION=${MATRIX_TARGET#*#} cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}-${{ matrix.special_features }}.info \ --partition count:${PARTITION}/4 \ - --workspace --features "cairo-1-hints, test_utils, ${{ matrix.special_features }}" + --workspace --features "cairo-1-hints, test_utils, function_runner, ${{ matrix.special_features }}" - name: Save coverage uses: actions/cache/save@v3 @@ -409,7 +417,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true # This is not pretty, but we need `make` to see the compiled programs are @@ -456,7 +464,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: ${{ matrix.program-target }}-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Generate traces @@ -675,7 +683,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Run script @@ -715,7 +723,7 @@ jobs: uses: actions/cache/restore@v3 with: path: ${{ env.CAIRO_PROGRAMS_PATH }} - key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'Makefile', 'requirements.txt') }} + key: cairo_proof_programs-cache-${{ hashFiles('cairo_programs/**/*.cairo', 'vm/src/tests/cairo_test_suite/**/*.cairo', 'Makefile', 'requirements.txt') }} fail-on-cache-miss: true - name: Fetch pie diff --git a/Makefile b/Makefile index 1734325614..dbf76a3ab2 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ UNAME := $(shell uname) compare_trace_memory compare_trace compare_memory compare_pie compare_all_no_proof \ compare_trace_memory_proof compare_all_proof compare_trace_proof compare_memory_proof compare_air_public_input compare_air_private_input\ hyper-threading-benchmarks \ - cairo_bench_programs cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_2_test_contracts \ + cairo_bench_programs cairo_proof_programs cairo_test_programs cairo_test_suite_programs cairo_1_test_contracts cairo_2_test_contracts \ cairo_trace cairo-vm_trace cairo_proof_trace cairo-vm_proof_trace python-deps python-deps-macos \ build-cairo-lang hint-accountant \ create-proof-programs-symlinks \ $(RELBIN) $(DBGBIN) @@ -124,6 +124,10 @@ NORETROCOMPAT_DIR:=cairo_programs/noretrocompat NORETROCOMPAT_FILES:=$(wildcard $(NORETROCOMPAT_DIR)/*.cairo) COMPILED_NORETROCOMPAT_TESTS:=$(patsubst $(NORETROCOMPAT_DIR)/%.cairo, $(NORETROCOMPAT_DIR)/%.json, $(NORETROCOMPAT_FILES)) +CAIRO_TEST_SUITE_ROOT:=vm/src/tests/cairo_test_suite +CAIRO_TEST_SUITE_FILES:=$(shell find $(CAIRO_TEST_SUITE_ROOT) -name "*.cairo") +COMPILED_CAIRO_TEST_SUITE:=$(patsubst %.cairo,%.json,$(CAIRO_TEST_SUITE_FILES)) + $(BENCH_DIR)/%.json: $(BENCH_DIR)/%.cairo cairo-compile --cairo_path="$(TEST_DIR):$(BENCH_DIR)" $< --output $@ --proof_mode @@ -145,6 +149,9 @@ $(BAD_TEST_DIR)/%.json: $(BAD_TEST_DIR)/%.cairo $(PRINT_TEST_DIR)/%.json: $(PRINT_TEST_DIR)/%.cairo cairo-compile $< --output $@ +$(CAIRO_TEST_SUITE_ROOT)/%.json: $(CAIRO_TEST_SUITE_ROOT)/%.cairo + cairo-compile $< --output $@ + # ====================== # Test Cairo 1 Contracts # ====================== @@ -268,6 +275,8 @@ run: check: cargo check +cairo_test_suite_programs: $(COMPILED_CAIRO_TEST_SUITE) + cairo_test_programs: $(COMPILED_TESTS) $(COMPILED_BAD_TESTS) $(COMPILED_NORETROCOMPAT_TESTS) $(COMPILED_PRINT_TESTS) $(COMPILED_MOD_BUILTIN_TESTS) $(COMPILED_SECP_CAIRO0_HINTS) $(COMPILED_KZG_DA_CAIRO0_HINTS) $(COMPILED_SEGMENT_ARENA_CAIRO0_HINTS) cairo_proof_programs: $(COMPILED_PROOF_TESTS) $(COMPILED_MOD_BUILTIN_PROOF_TESTS) $(COMPILED_STWO_EXCLUSIVE_TESTS) cairo_bench_programs: $(COMPILED_BENCHES) @@ -286,7 +295,7 @@ ifdef TEST_COLLECT_COVERAGE TEST_COMMAND:=cargo llvm-cov nextest --no-report endif -test: cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_2_test_contracts cairo_1_program +test: cairo_proof_programs cairo_test_programs cairo_test_suite_programs cairo_1_test_contracts cairo_2_test_contracts cairo_1_program $(TEST_COMMAND) --workspace --features "test_utils, cairo-1-hints" test-extensive_hints: cairo_proof_programs cairo_test_programs cairo_1_test_contracts cairo_1_program cairo_2_test_contracts $(TEST_COMMAND) --workspace --features "test_utils, cairo-1-hints, cairo-0-secp-hints, cairo-0-data-availability-hints, extensive_hints" From 828320a88f5e556983b66dc3c4032fa52c5c9e45 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 17:19:03 +0300 Subject: [PATCH 15/31] chore: update CHANGELOG for PR #2380 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c26e7c2eb..32800e5a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. #### Upcoming Changes * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `function_runner` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) +* feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) From d8f7f68d2df6fda83a77fa4df16f7547018e404b Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 23:28:09 +0300 Subject: [PATCH 16/31] update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32800e5a5a..267072dc63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. #### Upcoming Changes * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `function_runner` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) + * feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) From f0bb2d03a370bbb2241876d55ea3f99f57388592 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 23:43:49 +0300 Subject: [PATCH 17/31] fix(ci): remove stale function_runner feature flag from coverage command Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/rust.yml | 2 +- CHANGELOG.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 52210d6d11..8ba9fd8c53 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -337,7 +337,7 @@ jobs: export PARTITION=${MATRIX_TARGET#*#} cargo llvm-cov nextest --lcov --output-path lcov-${{ matrix.target }}-${{ matrix.special_features }}.info \ --partition count:${PARTITION}/4 \ - --workspace --features "cairo-1-hints, test_utils, function_runner, ${{ matrix.special_features }}" + --workspace --features "cairo-1-hints, test_utils, ${{ matrix.special_features }}" - name: Save coverage uses: actions/cache/save@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 267072dc63..25bcc8ac3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,8 +11,7 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. --- #### Upcoming Changes -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq`, `load_cairo_program!` macro and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) * feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) From aa8d90db5eab8895b50708881eb5412962abc6bd Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 26 Mar 2026 14:34:28 +0200 Subject: [PATCH 18/31] feat: add math cairo tests under vm/src/tests/cairo_test_suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create cairo_test_suite module skeleton (mod.rs, test_math/mod.rs) - Add main_math_test.cairo stub (imports all math.cairo functions) - Add math_test_utils.rs (RC_BOUND, MAX_DIV, is_quad_residue_mod_prime) - Move test_math_cairo.rs from cairo_test_suite/, update all imports - cairo_vm:: → crate:: for all internal paths - crate::error_utils → crate::test_helpers::error_utils - cairo_function_runner → function_runner module path - Fix 5x runner.runner.vm → runner.vm (old struct design remnant) - Register cairo_test_suite mod behind function_runner feature Co-Authored-By: Claude Sonnet 4.6 --- vm/src/tests/cairo_test_suite/mod.rs | 2 + .../test_math/main_math_test.cairo | 26 + .../test_math/math_test_utils.rs | 20 + .../tests/cairo_test_suite/test_math/mod.rs | 2 + .../test_math/test_math_cairo.rs | 988 ++++++++++++++++++ vm/src/tests/mod.rs | 3 + 6 files changed, 1041 insertions(+) create mode 100644 vm/src/tests/cairo_test_suite/mod.rs create mode 100644 vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo create mode 100644 vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs create mode 100644 vm/src/tests/cairo_test_suite/test_math/mod.rs create mode 100644 vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs diff --git a/vm/src/tests/cairo_test_suite/mod.rs b/vm/src/tests/cairo_test_suite/mod.rs new file mode 100644 index 0000000000..110584fc80 --- /dev/null +++ b/vm/src/tests/cairo_test_suite/mod.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "function_runner")] +mod test_math; diff --git a/vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo b/vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo new file mode 100644 index 0000000000..592a33c126 --- /dev/null +++ b/vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo @@ -0,0 +1,26 @@ +%builtins range_check + +from starkware.cairo.common.math import ( + assert_not_zero, + assert_not_equal, + assert_nn, + assert_le, + assert_lt, + assert_nn_le, + assert_in_range, + assert_250_bit, + split_felt, + assert_le_felt, + assert_lt_felt, + abs_value, + sign, + unsigned_div_rem, + signed_div_rem, + split_int, + sqrt, + is_quad_residue, +) + +func main{range_check_ptr}() { + return (); +} diff --git a/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs new file mode 100644 index 0000000000..aa79815475 --- /dev/null +++ b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs @@ -0,0 +1,20 @@ +use std::sync::LazyLock; + +use crate::{math_utils::is_quad_residue, utils::CAIRO_PRIME}; +use num_bigint::BigUint; +use num_integer::Integer; + +/// RC_BOUND = 2^128 +pub static RC_BOUND: LazyLock = LazyLock::new(|| BigUint::from(2u64).pow(128)); + +/// MAX_DIV = CAIRO_PRIME // RC_BOUND +pub static MAX_DIV: LazyLock = LazyLock::new(|| CAIRO_PRIME.div_floor(&RC_BOUND)); + +/// Returns 1 if `a` is a quadratic residue modulo CAIRO_PRIME, 0 if not, and -1 on error. +pub fn is_quad_residue_mod_prime(a: &BigUint) -> i64 { + match is_quad_residue(a, &CAIRO_PRIME) { + Ok(true) => 1, + Ok(false) => 0, + Err(_) => -1, + } +} diff --git a/vm/src/tests/cairo_test_suite/test_math/mod.rs b/vm/src/tests/cairo_test_suite/test_math/mod.rs new file mode 100644 index 0000000000..a68b4b985f --- /dev/null +++ b/vm/src/tests/cairo_test_suite/test_math/mod.rs @@ -0,0 +1,2 @@ +mod math_test_utils; +mod test_math_cairo; diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs new file mode 100644 index 0000000000..f5da76c18a --- /dev/null +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -0,0 +1,988 @@ +//! Tests for `math.cairo`. + +use std::sync::LazyLock; + +use super::math_test_utils::{is_quad_residue_mod_prime, MAX_DIV, RC_BOUND}; +use crate::test_helpers::error_utils::{ + expect_assert_lt_felt252, expect_assert_not_equal_fail, expect_diff_index_comp, + expect_diff_type_comparison, expect_hint_assert_not_zero, expect_hint_out_of_valid_range, + expect_hint_value_outside_250_bit_range, expect_hint_value_outside_valid_range, + expect_non_le_felt252, expect_ok, expect_split_int_limb_out_of_range, + expect_split_int_not_zero, VmCheck, +}; +use crate::cairo_args; +use crate::types::builtin_name::BuiltinName; +use crate::types::program::Program; +use crate::types::relocatable::MaybeRelocatable; +use crate::utils::CAIRO_PRIME; +use crate::vm::runners::function_runner::CairoFunctionRunner; +use crate::Felt252; +use num_bigint::{BigInt, BigUint, RandBigInt}; +use num_traits::{One, Signed, Zero}; +use rand::thread_rng; +use rstest::{fixture, rstest}; + +// ===================== Shared constants (LazyLock) ===================== + +/// The compiled Cairo math program, loaded once and shared across all tests. +static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("main_math_test.json")); + +/// Interesting felt values used in several tests. +static INTERESTING_FELTS: LazyLock> = LazyLock::new(|| { + let p = &*CAIRO_PRIME; + vec![ + BigUint::from(0u64), + BigUint::from(1u64), + BigUint::from(2u64).pow(128) - BigUint::one(), + BigUint::from(2u64).pow(128), + BigUint::from(2u64).pow(128) + BigUint::one(), + p / BigUint::from(3u64) - BigUint::one(), + p / BigUint::from(3u64), + p / BigUint::from(3u64) + BigUint::one(), + p / BigUint::from(2u64) - BigUint::one(), + p / BigUint::from(2u64), + p / BigUint::from(2u64) + BigUint::one(), + BigUint::from(2u64).pow(251) - BigUint::one(), + BigUint::from(2u64).pow(251), + BigUint::from(2u64).pow(251) + BigUint::one(), + p - BigUint::from(2u64), + p - BigUint::one(), + ] +}); + +// ===================== Helpers ===================== + +// ===================== Fixture ===================== + +/// Creates a fresh CairoFunctionRunner from the shared PROGRAM. +#[fixture] +fn runner() -> CairoFunctionRunner { + CairoFunctionRunner::new(&PROGRAM).unwrap() +} + +// ===================== test_assert_not_zero ===================== + +#[rstest] +// Case: value=7 +// Expected: Success. +#[case(Some(BigUint::from(7u64)), expect_ok)] +// Case: value=random +// Expected: Success. +#[case::random(None, expect_ok)] +// Case: value=0 +// Expected: Error. +#[case(Some(BigUint::zero()), expect_hint_assert_not_zero)] +fn test_assert_not_zero(#[case] value: Option, #[case] check: VmCheck<()>) { + let value = match value { + Some(v) => v, + None => { + let mut rng = thread_rng(); + rng.gen_biguint_range(&BigUint::one(), &CAIRO_PRIME) + } + }; + + let mut runner = runner(); + let args = cairo_args!(value); + let res = runner.run_default_cairo0("assert_not_zero", &args); + check(&res); +} + +// ===================== test_assert_not_equal ===================== + +#[rstest] +// Not equal integers +// Case: a=3, b=7 +// Expected: Success. +#[case::not_equal_ints(MaybeRelocatable::from(3), MaybeRelocatable::from(7), expect_ok)] +// Not equal relocatables (same segment, different offset) +// Case: a=(2, 5), b=(2, 10) +// Expected: Success. +#[case::not_equal_relocs( + MaybeRelocatable::from((2isize, 5)), + MaybeRelocatable::from((2isize, 10)), + expect_ok +)] +// Equal integers +// Case: a=5, b=5 +// Expected: Error. +#[case::equal_ints( + MaybeRelocatable::from(5), + MaybeRelocatable::from(5), + expect_assert_not_equal_fail +)] +// Equal relocatables +// Case: a=(1, 5), b=(1, 5) +// Expected: Error. +#[case::equal_relocs( + MaybeRelocatable::from((1isize, 5)), + MaybeRelocatable::from((1isize, 5)), + expect_assert_not_equal_fail +)] +// Non-comparable: relocatable vs int +// Case: a=(1, 5), b=0 +// Expected: Error. +#[case::non_comparable_reloc_vs_int( + MaybeRelocatable::from((1isize, 5)), + MaybeRelocatable::from(0), + expect_diff_type_comparison +)] +// Non-comparable: different segments +// Case: a=(1, 5), b=(2, 3) +// Expected: Error. +#[case::non_comparable_diff_segments( + MaybeRelocatable::from((1isize, 5)), + MaybeRelocatable::from((2isize, 3)), + expect_diff_index_comp +)] + +fn test_assert_not_equal( + #[case] a: MaybeRelocatable, + #[case] b: MaybeRelocatable, + #[case] check: VmCheck<()>, +) { + let mut runner = runner(); + let args = cairo_args!(a, b); + let res = runner.run_default_cairo0("assert_not_equal", &args); + check(&res); +} + +// ===================== test_assert_250_bit ===================== +#[rstest] +// Valid cases (should pass) +// Case: value=0 +// Expected: Success. +#[case::zero(BigUint::from(0u64), expect_ok)] +// Case: value=1 +// Expected: Success. +#[case::one(BigUint::from(1u64), expect_ok)] +// Case: value=(2^250)-1 +// Expected: Success. +#[case::max_valid(BigUint::from(2u64).pow(250) - BigUint::one(), expect_ok)] +// Invalid cases (should fail) +// Case: value=2^250 +// Expected: Error. +#[case::at_boundary(BigUint::from(2u64).pow(250), expect_hint_value_outside_250_bit_range)] +// Case: value=(2^250)+1 +// Expected: Error. +#[case::above_boundary(BigUint::from(2u64).pow(250) + BigUint::one(), expect_hint_value_outside_250_bit_range)] +// Case: value=2^251 +// Expected: Error. +#[case::way_above(BigUint::from(2u64).pow(251), expect_hint_value_outside_250_bit_range)] +// Case: value=PRIME-1 +// Expected: Error. +#[case::near_prime(&*CAIRO_PRIME - BigUint::one(), expect_hint_value_outside_250_bit_range)] +fn test_assert_250_bit( + mut runner: CairoFunctionRunner, + #[case] value: BigUint, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let args = cairo_args!(rc_base.clone(), value); + let res = runner.run_default_cairo0("assert_250_bit", &args); + check(&res); + + // If successful, verify the return value + if res.is_ok() { + let ret = runner.get_return_values(1).unwrap(); + assert_mr_eq!(&ret[0], &rc_base.add_usize(3usize).unwrap()); + } +} + +// ===================== test_split_felt ===================== + +#[rstest] +// Case: idx=0 +// Expected: Success. +#[case::idx_0(0)] +// Case: idx=1 +// Expected: Success. +#[case::idx_1(1)] +// Case: idx=2 +// Expected: Success. +#[case::idx_2(2)] +// Case: idx=3 +// Expected: Success. +#[case::idx_3(3)] +// Case: idx=4 +// Expected: Success. +#[case::idx_4(4)] +// Case: idx=5 +// Expected: Success. +#[case::idx_5(5)] +// Case: idx=6 +// Expected: Success. +#[case::idx_6(6)] +// Case: idx=7 +// Expected: Success. +#[case::idx_7(7)] +// Case: idx=8 +// Expected: Success. +#[case::idx_8(8)] +// Case: idx=9 +// Expected: Success. +#[case::idx_9(9)] +// Case: idx=10 +// Expected: Success. +#[case::idx_10(10)] +// Case: idx=11 +// Expected: Success. +#[case::idx_11(11)] +// Case: idx=12 +// Expected: Success. +#[case::idx_12(12)] +// Case: idx=13 +// Expected: Success. +#[case::idx_13(13)] +// Case: idx=14 +// Expected: Success. +#[case::idx_14(14)] +// Case: idx=15 +// Expected: Success. +#[case::idx_15(15)] +fn test_split_felt(mut runner: CairoFunctionRunner, #[case] idx: usize) { + let mask_128 = BigUint::from(2u64).pow(128) - BigUint::one(); + let value = &INTERESTING_FELTS[idx]; + + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let expected_high: BigUint = value >> 128; + let expected_low = value & &mask_128; + + let args = cairo_args!(rc_base.clone(), value); + runner + .run_default_cairo0("split_felt", &args) + .unwrap_or_else(|e| panic!("split_felt failed for value {value}: {e}")); + + let ret = runner.get_return_values(3).unwrap(); + // ret = [range_check_ptr, high, low] + assert_mr_eq!( + &ret[0], + &rc_base.add_usize(3usize).unwrap(), + "range_check_ptr mismatch for value {value}" + ); + assert_mr_eq!(&ret[1], &expected_high, "high mismatch for value {value}"); + assert_mr_eq!(&ret[2], &expected_low, "low mismatch for value {value}"); +} + +// ===================== test_assert_le_felt ===================== + +#[rstest] +fn test_assert_le_felt( + mut runner: CairoFunctionRunner, + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx0: usize, + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx1: usize, +) { + let value0 = &INTERESTING_FELTS[idx0]; + let value1 = &INTERESTING_FELTS[idx1]; + + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let args = cairo_args!(rc_base.clone(), value0, value1); + + if value0 <= value1 { + runner + .run_default_cairo0("assert_le_felt", &args) + .unwrap_or_else(|e| panic!("assert_le_felt failed for {value0} <= {value1}: {e}")); + let ret = runner.get_return_values(1).unwrap(); + assert_mr_eq!( + &ret[0], + &rc_base.add_usize(4usize).unwrap(), + "range_check_ptr mismatch for {value0} <= {value1}" + ); + } else { + let result = runner.run_default_cairo0("assert_le_felt", &args); + expect_non_le_felt252(&result); + } +} + +// ===================== test_assert_lt_felt ===================== + +#[rstest] +fn test_assert_lt_felt( + mut runner: CairoFunctionRunner, + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx0: usize, + #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx1: usize, +) { + let value0 = &INTERESTING_FELTS[idx0]; + let value1 = &INTERESTING_FELTS[idx1]; + + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let args = cairo_args!(rc_base.clone(), value0, value1); + + if value0 < value1 { + runner + .run_default_cairo0("assert_lt_felt", &args) + .unwrap_or_else(|e| panic!("assert_lt_felt failed for {value0} < {value1}: {e}")); + let ret = runner.get_return_values(1).unwrap(); + assert_mr_eq!( + &ret[0], + &rc_base.add_usize(4usize).unwrap(), + "range_check_ptr mismatch for {value0} < {value1}" + ); + } else { + let result = runner.run_default_cairo0("assert_lt_felt", &args); + expect_assert_lt_felt252(&result); + } +} + +// ===================== test_abs_value ===================== + +#[rstest] +// Case: value_case=17 +// Expected: Success. +#[case(BigInt::from(17), expect_ok)] +// Case: value_case=-42 +// Expected: Success. +#[case(BigInt::from(-42), expect_ok)] +// Case: value_case=0 +// Expected: Success. +#[case(BigInt::from(0), expect_ok)] +// Case: value_case=RC_BOUND +// Expected: Error. +#[case(BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] +// Case: value_case=-RC_BOUND +// Expected: Error. +#[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] +fn test_abs_value( + mut runner: CairoFunctionRunner, + #[case] value_case: BigInt, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + let rc_bound_biguint = runner + .vm + .get_range_check_builtin() + .expect("range_check builtin not found") + .bound() + .to_biguint(); + + let args = cairo_args!(rc_base.clone(), value_case.clone()); + let result = runner.run_default_cairo0("abs_value", &args); + check(&result); + let abs_value = value_case.magnitude(); + if abs_value < &rc_bound_biguint { + let ret = runner.get_return_values(2).unwrap(); + assert_mr_eq!(&ret[0], &rc_base.add_usize(1usize).unwrap()); + assert_mr_eq!(&ret[1], abs_value); + } +} + +// ===================== test_sign ===================== +#[rstest] +// Case: value_case=17 +// Expected: Success. +#[case(BigInt::from(17), expect_ok)] +// Case: value_case=-42 +// Expected: Success. +#[case(BigInt::from(-42), expect_ok)] +// Case: value_case=0 +// Expected: Success. +#[case(BigInt::from(0), expect_ok)] +// Case: value_case=RC_BOUND +// Expected: Error. +#[case(BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] +// Case: value_case=-RC_BOUND +// Expected: Error. +#[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] +fn test_sign( + mut runner: CairoFunctionRunner, + #[case] value_case: BigInt, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + let rc_bound_biguint = runner + .vm + .get_range_check_builtin() + .expect("range_check builtin not found") + .bound() + .to_biguint(); + + let args = cairo_args!(rc_base.clone(), value_case.clone()); + let result = runner.run_default_cairo0("sign", &args); + check(&result); + let abs_value = value_case.magnitude(); + if abs_value < &rc_bound_biguint { + let ret = runner.get_return_values(2).unwrap(); + // range_check_ptr == rc_base + (1 if value != 0 else 0) + let expected_rc_ptr = if value_case.is_zero() { + rc_base + } else { + rc_base.add_usize(1usize).unwrap() + }; + assert_mr_eq!(&ret[0], &expected_rc_ptr); + + // res == (0 if value == 0 else 1 if value > 0 else PRIME - 1) + let expected_sign = if value_case.is_zero() { + BigUint::zero() + } else if value_case.is_positive() { + BigUint::one() + } else { + &*CAIRO_PRIME - BigUint::one() + }; + assert_mr_eq!(&ret[1], &expected_sign); + } +} + +// ===================== test_unsigned_div_rem ===================== + +#[rstest] +// 1) q=1333, div=17, r=3 +// Case: q=1333, div=17, r=3 +// Expected: Success. +#[case::case_1_basic( + Some(BigUint::from(1333u64)), + Some(BigUint::from(17u64)), + Some(BigUint::from(3u64)), + expect_ok +)] +// 2) q=RC_BOUND-1, div=MAX_DIV, r=MAX_DIV-1 +// Case: q=RC_BOUND-1, div=MAX_DIV, r=MAX_DIV-1 +// Expected: Success. +#[case::case_2_max_values( + Some(&*RC_BOUND - BigUint::one()), + Some(MAX_DIV.clone()), + Some(&*MAX_DIV - BigUint::one()), + expect_ok +)] +// 3) q=random, div=MAX_DIV, r=0 +// Case: q=random, div=MAX_DIV, r=0 +// Expected: Success. +#[case::case_3_random_q( + None, + Some(MAX_DIV.clone()), + Some(BigUint::zero()), + expect_ok +)] +// 4) q=random, div=MAX_DIV, r=MAX_DIV-1 +// Case: q=random, div=MAX_DIV, r=MAX_DIV-1 +// Expected: Success. +#[case::case_4_random_q( + None, + Some(MAX_DIV.clone()), + Some(&*MAX_DIV - BigUint::one()), + expect_ok +)] +// 5) q=random, div=MAX_DIV, r=random +// Case: q=random, div=MAX_DIV, r=random +// Expected: Success. +#[case::case_5_random_q_and_r( + None, + Some(MAX_DIV.clone()), + None, + expect_ok +)] +// 6) q=random, div=random, r=random +// Case: q=random, div=random, r=random +// Expected: Success. +#[case::case_6_all_random(None, None, None, expect_ok)] +// 7) q=1, div=MAX_DIV+1, r=random -> expected error. +// Case: q=1, div=MAX_DIV+1, r=random +// Expected: Error. +#[case::case_7_invalid_div( + Some(BigUint::one()), + Some(&*MAX_DIV + BigUint::one()), + None, + expect_hint_out_of_valid_range +)] +fn test_unsigned_div_rem( + mut runner: CairoFunctionRunner, + #[case] q: Option, + #[case] div: Option, + #[case] r: Option, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + // Verify rc_bound matches expected RC_BOUND (2^128) + let rc_bound = runner + .vm + .get_range_check_builtin() + .expect("range_check builtin not found") + .bound() + .to_biguint(); + assert_eq!(rc_bound, *RC_BOUND, "Unexpected rc_bound"); + + let mut rng = thread_rng(); + // Python uses div in [0, MAX_DIV], but remainder generation requires div > 0. + let div = match div { + Some(v) => v, + None => rng.gen_biguint_range(&BigUint::one(), &(&*MAX_DIV + BigUint::one())), + }; + let r = match r { + Some(v) => v, + None => rng.gen_biguint_range(&BigUint::zero(), &div), + }; + let q = match q { + Some(v) => v, + None => rng.gen_biguint_range(&BigUint::zero(), &RC_BOUND), + }; + + let value = &q * &div + &r; + + // Assert value < PRIME (as in Python test) + assert!( + value < *CAIRO_PRIME, + "Generated value is too large. q={q}, div={div}, r={r}" + ); + + let args = cairo_args!(rc_base.clone(), value, div); + let result = runner.run_default_cairo0("unsigned_div_rem", &args); + check(&result); + + // If successful, verify the results match expected values + if result.is_ok() { + let ret = runner.get_return_values(3).unwrap(); + assert_mr_eq!( + &ret[0], + &rc_base.add_usize(3usize).unwrap(), + "range_check_ptr mismatch" + ); + assert_mr_eq!(&ret[1], &q, "quotient mismatch"); + assert_mr_eq!(&ret[2], &r, "remainder mismatch"); + } +} + +// ===================== test_signed_div_rem ===================== +#[rstest] +// Case: q=1333, div=17, r=3, bound=random in chosen range [q+1,RC_BOUND/2]) +// Expected: Success. +#[case::basic( + Some(BigInt::from(1333)), + Some(BigUint::from(17u64)), + Some(BigUint::from(3u64)), + None, + expect_ok +)] +// Case: q=-1333, div=17, r=3, bound=random in chosen range [-q,RC_BOUND/2]) +// Expected: Success. +#[case::negative_basic( + Some(BigInt::from(-1333)), + Some(BigUint::from(17u64)), + Some(BigUint::from(3u64)), + None, + expect_ok +)] +// Case: q=RC_BOUND/2-1, div=MAX_DIV, r=MAX_DIV-1, bound=random in chosen range [q+1,RC_BOUND/2]) +// Expected: Success. +#[case::max_pos( + Some(BigInt::from(&*RC_BOUND / BigUint::from(2u64) - BigUint::one())), + Some(MAX_DIV.clone()), + Some(&*MAX_DIV - BigUint::one()), + None, + expect_ok +)] +// Case: q=-(RC_BOUND/2)+1, div=MAX_DIV, r=0, bound=random in chosen range [-q,RC_BOUND/2]) +// Expected: Success. +#[case::max_neg( + Some(-(BigInt::from(&*RC_BOUND / BigUint::from(2u64))) + BigInt::one()), + Some(MAX_DIV.clone()), + Some(BigUint::zero()), + None, + expect_ok +)] +// Case: q=random, div=MAX_DIV, r=0, bound=random in chosen range ([q+1,RC_BOUND/2] or +// [-q,RC_BOUND/2]) Expected: Success. +#[case::random_q_max_div_r_zero(None, Some(MAX_DIV.clone()), Some(BigUint::zero()), None, expect_ok)] +// Case: q=random, div=MAX_DIV, r=MAX_DIV-1, bound=random in chosen range ([q+1,RC_BOUND/2] or +// [-q,RC_BOUND/2]) Expected: Success. +#[case::random_q_max_div_r_max(None, Some(MAX_DIV.clone()), Some(&*MAX_DIV - BigUint::one()), None, expect_ok)] +// Case: q=random, div=MAX_DIV, r=random, bound=random in chosen range ([q+1,RC_BOUND/2] or +// [-q,RC_BOUND/2]) Expected: Success. +#[case::random_q_max_div_random_r(None, Some(MAX_DIV.clone()), None, None, expect_ok)] +// Case: q=RC_BOUND/2-1, div=random, r=random, bound=RC_BOUND/2 +// Expected: Success. +#[case::bound_eq_half_pos_q( + Some(BigInt::from(&*RC_BOUND / BigUint::from(2u64) - BigUint::one())), + None, + None, + Some(&*RC_BOUND / BigUint::from(2u64)), + expect_ok +)] +// Case: q=-RC_BOUND/2, div=random, r=random, bound=RC_BOUND/2 +// Expected: Success. +#[case::bound_eq_half_neg_q( + Some(-BigInt::from(&*RC_BOUND / BigUint::from(2u64))), + None, + None, + Some(&*RC_BOUND / BigUint::from(2u64)), + expect_ok +)] +// Case: q=1, div=MAX_DIV+1, r=random, bound=random in chosen range [q+1,RC_BOUND/2]) +// Expected: Error. +#[case::invalid_div( + Some(BigInt::one()), + Some(&*MAX_DIV + BigUint::one()), + None, + None, + expect_hint_out_of_valid_range +)] +// Case: q=random, div=random, r=random, bound=RC_BOUND/2+1 +// Expected: Error. +#[case::invalid_bound( + None, + None, + None, + Some(&*RC_BOUND / BigUint::from(2u64) + BigUint::one()), + expect_hint_out_of_valid_range +)] +fn test_signed_div_rem( + mut runner: CairoFunctionRunner, + #[case] q: Option, + #[case] div: Option, + #[case] r: Option, + #[case] bound: Option, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let half_rc_bound = &*RC_BOUND / BigUint::from(2u64); + let mut rng = thread_rng(); + + let div = match div { + Some(v) => v, + None => rng.gen_biguint_range(&BigUint::one(), &(&*MAX_DIV + BigUint::one())), + }; + let r = match r { + Some(v) => v, + None => rng.gen_biguint_range(&BigUint::zero(), &div), + }; + let q = match q { + Some(v) => v, + None => { + let min = -BigInt::from(half_rc_bound.clone()); + let max = BigInt::from(half_rc_bound.clone()); + rng.gen_bigint_range(&min, &max) + } + }; + let bound = match bound { + Some(v) => v, + None => { + let lower = if q >= BigInt::zero() { + q.clone() + BigInt::one() + } else { + -q.clone() + }; + let upper = BigInt::from(half_rc_bound.clone() + BigUint::one()); + rng.gen_bigint_range(&lower, &upper) + .to_biguint() + .expect("bound should be non-negative") + } + }; + + let value = q.clone() * BigInt::from(div.clone()) + BigInt::from(r.clone()); + let half_prime = BigInt::from((&*CAIRO_PRIME) >> 1usize); + let neg_half_prime = -half_prime.clone(); + assert!( + value >= neg_half_prime && value < half_prime, + "Generated value is too large." + ); + + let args = cairo_args!(rc_base.clone(), value, div, bound); + let result = runner.run_default_cairo0("signed_div_rem", &args); + check(&result); + + if result.is_ok() { + let ret = runner.get_return_values(3).unwrap(); + let rc_ptr = &ret[0]; + let result_q = &ret[1]; + let result_r = &ret[2]; + + assert_mr_eq!(rc_ptr, &rc_base.add_usize(4usize).unwrap()); + // Expected_q = q % PRIME (field element conversion). + let expected_q = Felt252::from(&q); + assert_mr_eq!(result_q, &expected_q); + assert_mr_eq!(result_r, &r); + } +} + +// ===================== test_split_int ===================== +#[rstest] +// Case: value=0x1234FCDA, n=10, base=16, bound=16, expected_output=vec![0xA, 0xD, +// 0xC, 0xF, 0x4, 0x3, 0x2, 0x1, 0, 0] Expected: Success. +#[case::hex_digits( + 0x1234FCDA_i64, + 10_i64, + 16_i64, + 16_i64, + Some(vec![0xA, 0xD, 0xC, 0xF, 0x4, 0x3, 0x2, 0x1, 0, 0]), + expect_ok +)] +// Case: value=0x1234FCDA, n=10, base=256, bound=256, expected_output=vec![0xDA, +// 0xFC, 0x34, 0x12, 0, 0, 0, 0, 0, 0] Expected: Success. +#[case::byte_pairs( + 0x1234FCDA_i64, + 10_i64, + 256_i64, + 256_i64, + Some(vec![0xDA, 0xFC, 0x34, 0x12, 0, 0, 0, 0, 0, 0]), + expect_ok +)] +// Case: value=0x1234FCDA, n=10, base=16, bound=15, expected_output=random +// Expected: Error. +#[case::out_of_bound_limb( + 0x1234FCDA_i64, + 10_i64, + 16_i64, + 15_i64, + None, + expect_split_int_limb_out_of_range +)] +// Case: value=0xAAA, n=3, base=16, bound=11, expected_output=vec![0xA, 0xA, 0xA] +// Expected: Success. +#[case::exact_fit( + 0xAAA_i64, + 3_i64, + 16_i64, + 11_i64, + Some(vec![0xA, 0xA, 0xA]), + expect_ok +)] +// Case: value=0xAAA, n=3, base=16, bound=10, expected_output=random +// Expected: Error. +#[case::bound_too_small( + 0xAAA_i64, + 3_i64, + 16_i64, + 10_i64, + None, + expect_split_int_limb_out_of_range +)] +// Case: value=0xAAA, n=2, base=16, bound=16, expected_output=random +// Expected: Error. +#[case::value_out_of_range(0xAAA_i64, 2_i64, 16_i64, 16_i64, None, expect_split_int_not_zero)] +fn test_split_int( + mut runner: CairoFunctionRunner, + #[case] value: i64, + #[case] n: i64, + #[case] base: i64, + #[case] bound: i64, + #[case] expected_output: Option>, + #[case] check: VmCheck<()>, +) { + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let output = runner.vm.add_memory_segment(); + let output_mr = MaybeRelocatable::from(output); + + let args = cairo_args!(rc_base.clone(), value, n, base, bound, output_mr); + let result = runner.run_default_cairo0("split_int", &args); + check(&result); + + if result.is_ok() { + let expected_output = + expected_output.expect("expected_output must be set for success case"); + let ret = runner.get_return_values(1).unwrap(); + assert_mr_eq!(&ret[0], &rc_base.add_usize(2usize * n as usize).unwrap()); + + let range = runner.vm.get_range(output, n as usize); + assert_eq!( + range.len(), + expected_output.len(), + "split_int output length mismatch" + ); + for (i, (actual, exp)) in range.iter().zip(expected_output.iter()).enumerate() { + let actual_val = actual + .as_ref() + .unwrap_or_else(|| panic!("Missing output at index {i}")); + assert_mr_eq!( + actual_val.as_ref(), + *exp, + "split_int output mismatch at index {i}" + ); + } + } +} +// ===================== test_sqrt ===================== + +#[rstest] +// Case: value=0 +// Expected: Success. +#[case::zero(Some(BigUint::from(0u64)), expect_ok)] +// Case: value=1 +// Expected: Success. +#[case::one(Some(BigUint::from(1u64)), expect_ok)] +// Case: value=2 +// Expected: Success. +#[case::two(Some(BigUint::from(2u64)), expect_ok)] +// Case: value=3 +// Expected: Success. +#[case::three(Some(BigUint::from(3u64)), expect_ok)] +// Case: value=4 +// Expected: Success. +#[case::four(Some(BigUint::from(4u64)), expect_ok)] +// Case: value=5 +// Expected: Success. +#[case::five(Some(BigUint::from(5u64)), expect_ok)] +// Case: value=6 +// Expected: Success. +#[case::six(Some(BigUint::from(6u64)), expect_ok)] +// Case: value=7 +// Expected: Success. +#[case::seven(Some(BigUint::from(7u64)), expect_ok)] +// Case: value=8 +// Expected: Success. +#[case::eight(Some(BigUint::from(8u64)), expect_ok)] +// Case: value=9 +// Expected: Success. +#[case::nine(Some(BigUint::from(9u64)), expect_ok)] +// Case: value=(2^250)-1 +// Expected: Success. +#[case::max_valid(Some(BigUint::from(2u64).pow(250) - BigUint::one()), expect_ok)] +// Case: value=random +// Expected: Success. +#[case::random(None, expect_ok)] +// Case: value=2^250 +// Expected: Error. +#[case::out_of_range_2_pow_250(Some(BigUint::from(2u64).pow(250)), expect_hint_value_outside_250_bit_range)] +// Case: value=PRIME-1 +// Expected: Error. +#[case::out_of_range_prime_minus_one( + Some(&*CAIRO_PRIME - BigUint::one()), + expect_hint_value_outside_250_bit_range +)] +fn test_sqrt( + mut runner: CairoFunctionRunner, + #[case] value: Option, + #[case] check: VmCheck<()>, +) { + let value = value.unwrap_or_else(|| { + let mut rng = thread_rng(); + let upper = BigUint::one() << 250usize; + rng.gen_biguint_range(&BigUint::zero(), &upper) + }); + + let rc_base = runner + .get_builtin_base(BuiltinName::range_check) + .expect("range_check builtin not found"); + + let args = cairo_args!(rc_base.clone(), value.clone()); + let result = runner.run_default_cairo0("sqrt", &args); + check(&result); + + if result.is_ok() { + let ret = runner.get_return_values(2).unwrap(); + assert_mr_eq!( + &ret[0], + &rc_base.add_usize(4usize).unwrap(), + "range_check_ptr mismatch for sqrt({value})" + ); + + let expected_root = value.sqrt(); + assert_mr_eq!( + &ret[1], + &expected_root, + "sqrt result mismatch for value={value}" + ); + } +} + +// ===================== test_horner_eval ===================== + +#[rstest] +// Case: n=0 +// Expected: Success. +#[case::zero_coefficients(0)] +// Case: n=16 +// Expected: Success. +#[case::sixteen_coefficients(16)] +fn test_horner_eval(mut runner: CairoFunctionRunner, #[case] n: usize) { + let mut rng = thread_rng(); + let prime = &*CAIRO_PRIME; + + // Generate random coefficients in [0, PRIME) + let coefficients: Vec = (0..n) + .map(|_| rng.gen_biguint_range(&BigUint::zero(), prime)) + .collect(); + let coeff_mr: Vec = coefficients.iter().map(MaybeRelocatable::from).collect(); + + // Generate random point in [0, PRIME) + let point = rng.gen_biguint_range(&BigUint::zero(), prime); + + // horner_eval takes (n, coefficients_ptr, point) - coefficients is an array + let args = cairo_args!(n, coeff_mr, point.clone()); + runner.run_default_cairo0("horner_eval", &args).unwrap(); + + let ret = runner.get_return_values(1).unwrap(); + + // Compute expected result: sum(coef * point^i for i, coef in enumerate(coefficients)) % PRIME + let expected: BigUint = coefficients + .iter() + .enumerate() + .map(|(i, coef)| coef * point.modpow(&BigUint::from(i), prime)) + .fold(BigUint::zero(), |acc, x| (acc + x) % prime); + + assert_mr_eq!(&ret[0], &expected); +} + +// ===================== test_is_quad_residue ===================== + +#[rstest] +// Case: x=0 +// Expected: Success. +#[case::zero(Some(BigUint::zero()))] +// Case: x=random +// Expected: Success. +#[case::random(None)] +fn test_is_quad_residue(mut runner: CairoFunctionRunner, #[case] x: Option) { + let prime = &*CAIRO_PRIME; + + let x = x.unwrap_or_else(|| { + let mut rng = thread_rng(); + rng.gen_biguint_range(&BigUint::one(), prime) + }); + + // Test is_quad_residue(x) + let args = cairo_args!(x.clone()); + runner.run_default_cairo0("is_quad_residue", &args).unwrap(); + let ret = runner.get_return_values(1).unwrap(); + + let expected = is_quad_residue_mod_prime(&x); + assert_mr_eq!( + &ret[0], + expected, + "is_quad_residue({x}) should return {expected}" + ); + + // Test is_quad_residue(3 * x) + // 3 is not a quadratic residue modulo PRIME + let mut runner2 = CairoFunctionRunner::new(&PROGRAM).unwrap(); + let three_x = (BigUint::from(3u64) * &x) % prime; + let args2 = cairo_args!(three_x); + runner2 + .run_default_cairo0("is_quad_residue", &args2) + .unwrap(); + let ret2 = runner2.get_return_values(1).unwrap(); + + let expected2 = if x.is_zero() { + 1i64 // 3 * 0 = 0, which is QR + } else if is_quad_residue_mod_prime(&x) == 1 { + 0i64 // x is QR, 3 is not QR, so 3*x is not QR + } else { + 1i64 // x is not QR, 3 is not QR, so 3*x is QR (product of two non-QR is QR) + }; + assert_mr_eq!( + &ret2[0], + expected2, + "is_quad_residue(3 * {x}) should return {expected2}" + ); +} diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 342b912d34..7648629512 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -36,6 +36,9 @@ mod cairo_pie_test; #[cfg(feature = "test_utils")] mod skip_instruction_test; +#[cfg(feature = "function_runner")] +mod cairo_test_suite; + //For simple programs that should just succeed and have no special needs. //Checks memory holes == 0 fn run_program_simple(data: &[u8]) { From 15d89e7a23d92364c4dabbbfb365f244fc0c6a00 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 26 Mar 2026 15:14:55 +0200 Subject: [PATCH 19/31] fix: correct CairoFunctionRunner usage in test_math_cairo.rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new() → new_for_testing() (type alias has no 1-arg constructor) - runner.get_return_values() → runner.vm.get_return_values() (method is on VirtualMachine) - Add missing macro imports: assert_mr_eq! and load_cairo_program! Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/test_math_cairo.rs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index f5da76c18a..7be8b8b58d 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -10,7 +10,9 @@ use crate::test_helpers::error_utils::{ expect_non_le_felt252, expect_ok, expect_split_int_limb_out_of_range, expect_split_int_not_zero, VmCheck, }; +use crate::assert_mr_eq; use crate::cairo_args; +use crate::load_cairo_program; use crate::types::builtin_name::BuiltinName; use crate::types::program::Program; use crate::types::relocatable::MaybeRelocatable; @@ -57,7 +59,7 @@ static INTERESTING_FELTS: LazyLock> = LazyLock::new(|| { /// Creates a fresh CairoFunctionRunner from the shared PROGRAM. #[fixture] fn runner() -> CairoFunctionRunner { - CairoFunctionRunner::new(&PROGRAM).unwrap() + CairoFunctionRunner::new_for_testing(&PROGRAM).unwrap() } // ===================== test_assert_not_zero ===================== @@ -186,7 +188,7 @@ fn test_assert_250_bit( // If successful, verify the return value if res.is_ok() { - let ret = runner.get_return_values(1).unwrap(); + let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!(&ret[0], &rc_base.add_usize(3usize).unwrap()); } } @@ -258,7 +260,7 @@ fn test_split_felt(mut runner: CairoFunctionRunner, #[case] idx: usize) { .run_default_cairo0("split_felt", &args) .unwrap_or_else(|e| panic!("split_felt failed for value {value}: {e}")); - let ret = runner.get_return_values(3).unwrap(); + let ret = runner.vm.get_return_values(3).unwrap(); // ret = [range_check_ptr, high, low] assert_mr_eq!( &ret[0], @@ -290,7 +292,7 @@ fn test_assert_le_felt( runner .run_default_cairo0("assert_le_felt", &args) .unwrap_or_else(|e| panic!("assert_le_felt failed for {value0} <= {value1}: {e}")); - let ret = runner.get_return_values(1).unwrap(); + let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!( &ret[0], &rc_base.add_usize(4usize).unwrap(), @@ -323,7 +325,7 @@ fn test_assert_lt_felt( runner .run_default_cairo0("assert_lt_felt", &args) .unwrap_or_else(|e| panic!("assert_lt_felt failed for {value0} < {value1}: {e}")); - let ret = runner.get_return_values(1).unwrap(); + let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!( &ret[0], &rc_base.add_usize(4usize).unwrap(), @@ -373,7 +375,7 @@ fn test_abs_value( check(&result); let abs_value = value_case.magnitude(); if abs_value < &rc_bound_biguint { - let ret = runner.get_return_values(2).unwrap(); + let ret = runner.vm.get_return_values(2).unwrap(); assert_mr_eq!(&ret[0], &rc_base.add_usize(1usize).unwrap()); assert_mr_eq!(&ret[1], abs_value); } @@ -416,7 +418,7 @@ fn test_sign( check(&result); let abs_value = value_case.magnitude(); if abs_value < &rc_bound_biguint { - let ret = runner.get_return_values(2).unwrap(); + let ret = runner.vm.get_return_values(2).unwrap(); // range_check_ptr == rc_base + (1 if value != 0 else 0) let expected_rc_ptr = if value_case.is_zero() { rc_base @@ -547,7 +549,7 @@ fn test_unsigned_div_rem( // If successful, verify the results match expected values if result.is_ok() { - let ret = runner.get_return_values(3).unwrap(); + let ret = runner.vm.get_return_values(3).unwrap(); assert_mr_eq!( &ret[0], &rc_base.add_usize(3usize).unwrap(), @@ -700,7 +702,7 @@ fn test_signed_div_rem( check(&result); if result.is_ok() { - let ret = runner.get_return_values(3).unwrap(); + let ret = runner.vm.get_return_values(3).unwrap(); let rc_ptr = &ret[0]; let result_q = &ret[1]; let result_r = &ret[2]; @@ -791,7 +793,7 @@ fn test_split_int( if result.is_ok() { let expected_output = expected_output.expect("expected_output must be set for success case"); - let ret = runner.get_return_values(1).unwrap(); + let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!(&ret[0], &rc_base.add_usize(2usize * n as usize).unwrap()); let range = runner.vm.get_range(output, n as usize); @@ -880,7 +882,7 @@ fn test_sqrt( check(&result); if result.is_ok() { - let ret = runner.get_return_values(2).unwrap(); + let ret = runner.vm.get_return_values(2).unwrap(); assert_mr_eq!( &ret[0], &rc_base.add_usize(4usize).unwrap(), @@ -922,7 +924,7 @@ fn test_horner_eval(mut runner: CairoFunctionRunner, #[case] n: usize) { let args = cairo_args!(n, coeff_mr, point.clone()); runner.run_default_cairo0("horner_eval", &args).unwrap(); - let ret = runner.get_return_values(1).unwrap(); + let ret = runner.vm.get_return_values(1).unwrap(); // Compute expected result: sum(coef * point^i for i, coef in enumerate(coefficients)) % PRIME let expected: BigUint = coefficients @@ -954,7 +956,7 @@ fn test_is_quad_residue(mut runner: CairoFunctionRunner, #[case] x: Option Date: Sun, 29 Mar 2026 11:13:23 +0300 Subject: [PATCH 20/31] fix(cairo_test_suite): rename cairo file and add horner_eval import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename main_math_test.cairo → math_test.cairo for clarity. Add `from starkware.cairo.common.math_utils import horner_eval` so the compiled JSON includes horner_eval (used in test_horner_eval). Update the load_cairo_program! call to reference math_test.json. Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/{main_math_test.cairo => math_test.cairo} | 2 ++ vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) rename vm/src/tests/cairo_test_suite/test_math/{main_math_test.cairo => math_test.cairo} (87%) diff --git a/vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo similarity index 87% rename from vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo rename to vm/src/tests/cairo_test_suite/test_math/math_test.cairo index 592a33c126..8796809f6d 100644 --- a/vm/src/tests/cairo_test_suite/test_math/main_math_test.cairo +++ b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo @@ -19,7 +19,9 @@ from starkware.cairo.common.math import ( split_int, sqrt, is_quad_residue, + ) +from starkware.cairo.common.math_utils import horner_eval func main{range_check_ptr}() { return (); diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index 7be8b8b58d..b972674153 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -27,7 +27,7 @@ use rstest::{fixture, rstest}; // ===================== Shared constants (LazyLock) ===================== /// The compiled Cairo math program, loaded once and shared across all tests. -static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("main_math_test.json")); +static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("math_test.json")); /// Interesting felt values used in several tests. static INTERESTING_FELTS: LazyLock> = LazyLock::new(|| { From e41b80f7343db5f0d79efaee61dcb4ed9b6814cc Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 11:19:10 +0300 Subject: [PATCH 21/31] fix(cairo_test_suite): import all functions from math.cairo Add missing assert_not_nullptr, safe_div, safe_mult. Move horner_eval and assert_is_power_of_2 into the math.cairo import block (both are defined there, not in math_utils). Match declaration order to match math.cairo. Co-Authored-By: Claude Sonnet 4.6 --- vm/src/tests/cairo_test_suite/test_math/math_test.cairo | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/math_test.cairo b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo index 8796809f6d..38e1c37e05 100644 --- a/vm/src/tests/cairo_test_suite/test_math/math_test.cairo +++ b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo @@ -2,6 +2,7 @@ from starkware.cairo.common.math import ( assert_not_zero, + assert_not_nullptr, assert_not_equal, assert_nn, assert_le, @@ -16,12 +17,15 @@ from starkware.cairo.common.math import ( sign, unsigned_div_rem, signed_div_rem, + safe_div, + safe_mult, split_int, sqrt, + horner_eval, is_quad_residue, - + assert_is_power_of_2, ) -from starkware.cairo.common.math_utils import horner_eval + func main{range_check_ptr}() { return (); From c7758917f5054d4394d0d2eddf00e439bae39bd1 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 13:12:18 +0300 Subject: [PATCH 22/31] refactor(cairo_test_suite): remove redundant numeric type suffixes - Remove isize from MaybeRelocatable tuple literals (inferred from From impl) - Remove usize from add_usize() calls (inferred from param type) - Remove _i64 suffixes in split_int cases (inferred from param types) - Remove usize from shift/bit operations - Use type annotation on variable instead of inline i64 suffixes on literals Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/test_math_cairo.rs | 100 +++++++++--------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index b972674153..dafaf5ba6b 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -33,8 +33,8 @@ static PROGRAM: LazyLock = LazyLock::new(|| load_cairo_program!("math_t static INTERESTING_FELTS: LazyLock> = LazyLock::new(|| { let p = &*CAIRO_PRIME; vec![ - BigUint::from(0u64), - BigUint::from(1u64), + BigUint::zero(), + BigUint::one(), BigUint::from(2u64).pow(128) - BigUint::one(), BigUint::from(2u64).pow(128), BigUint::from(2u64).pow(128) + BigUint::one(), @@ -100,8 +100,8 @@ fn test_assert_not_zero(#[case] value: Option, #[case] check: VmCheck<( // Case: a=(2, 5), b=(2, 10) // Expected: Success. #[case::not_equal_relocs( - MaybeRelocatable::from((2isize, 5)), - MaybeRelocatable::from((2isize, 10)), + MaybeRelocatable::from((2, 5)), + MaybeRelocatable::from((2, 10)), expect_ok )] // Equal integers @@ -116,15 +116,15 @@ fn test_assert_not_zero(#[case] value: Option, #[case] check: VmCheck<( // Case: a=(1, 5), b=(1, 5) // Expected: Error. #[case::equal_relocs( - MaybeRelocatable::from((1isize, 5)), - MaybeRelocatable::from((1isize, 5)), + MaybeRelocatable::from((1, 5)), + MaybeRelocatable::from((1, 5)), expect_assert_not_equal_fail )] // Non-comparable: relocatable vs int // Case: a=(1, 5), b=0 // Expected: Error. #[case::non_comparable_reloc_vs_int( - MaybeRelocatable::from((1isize, 5)), + MaybeRelocatable::from((1, 5)), MaybeRelocatable::from(0), expect_diff_type_comparison )] @@ -132,8 +132,8 @@ fn test_assert_not_zero(#[case] value: Option, #[case] check: VmCheck<( // Case: a=(1, 5), b=(2, 3) // Expected: Error. #[case::non_comparable_diff_segments( - MaybeRelocatable::from((1isize, 5)), - MaybeRelocatable::from((2isize, 3)), + MaybeRelocatable::from((1, 5)), + MaybeRelocatable::from((2, 3)), expect_diff_index_comp )] @@ -153,10 +153,10 @@ fn test_assert_not_equal( // Valid cases (should pass) // Case: value=0 // Expected: Success. -#[case::zero(BigUint::from(0u64), expect_ok)] +#[case::zero(BigUint::zero(), expect_ok)] // Case: value=1 // Expected: Success. -#[case::one(BigUint::from(1u64), expect_ok)] +#[case::one(BigUint::one(), expect_ok)] // Case: value=(2^250)-1 // Expected: Success. #[case::max_valid(BigUint::from(2u64).pow(250) - BigUint::one(), expect_ok)] @@ -189,7 +189,7 @@ fn test_assert_250_bit( // If successful, verify the return value if res.is_ok() { let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(3usize).unwrap()); + assert_mr_eq!(&ret[0], &rc_base.add_usize(3).unwrap()); } } @@ -264,7 +264,7 @@ fn test_split_felt(mut runner: CairoFunctionRunner, #[case] idx: usize) { // ret = [range_check_ptr, high, low] assert_mr_eq!( &ret[0], - &rc_base.add_usize(3usize).unwrap(), + &rc_base.add_usize(3).unwrap(), "range_check_ptr mismatch for value {value}" ); assert_mr_eq!(&ret[1], &expected_high, "high mismatch for value {value}"); @@ -295,7 +295,7 @@ fn test_assert_le_felt( let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!( &ret[0], - &rc_base.add_usize(4usize).unwrap(), + &rc_base.add_usize(4).unwrap(), "range_check_ptr mismatch for {value0} <= {value1}" ); } else { @@ -328,7 +328,7 @@ fn test_assert_lt_felt( let ret = runner.vm.get_return_values(1).unwrap(); assert_mr_eq!( &ret[0], - &rc_base.add_usize(4usize).unwrap(), + &rc_base.add_usize(4).unwrap(), "range_check_ptr mismatch for {value0} < {value1}" ); } else { @@ -376,7 +376,7 @@ fn test_abs_value( let abs_value = value_case.magnitude(); if abs_value < &rc_bound_biguint { let ret = runner.vm.get_return_values(2).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(1usize).unwrap()); + assert_mr_eq!(&ret[0], &rc_base.add_usize(1).unwrap()); assert_mr_eq!(&ret[1], abs_value); } } @@ -423,7 +423,7 @@ fn test_sign( let expected_rc_ptr = if value_case.is_zero() { rc_base } else { - rc_base.add_usize(1usize).unwrap() + rc_base.add_usize(1).unwrap() }; assert_mr_eq!(&ret[0], &expected_rc_ptr); @@ -552,7 +552,7 @@ fn test_unsigned_div_rem( let ret = runner.vm.get_return_values(3).unwrap(); assert_mr_eq!( &ret[0], - &rc_base.add_usize(3usize).unwrap(), + &rc_base.add_usize(3).unwrap(), "range_check_ptr mismatch" ); assert_mr_eq!(&ret[1], &q, "quotient mismatch"); @@ -690,7 +690,7 @@ fn test_signed_div_rem( }; let value = q.clone() * BigInt::from(div.clone()) + BigInt::from(r.clone()); - let half_prime = BigInt::from((&*CAIRO_PRIME) >> 1usize); + let half_prime = BigInt::from((&*CAIRO_PRIME) >> 1); let neg_half_prime = -half_prime.clone(); assert!( value >= neg_half_prime && value < half_prime, @@ -707,7 +707,7 @@ fn test_signed_div_rem( let result_q = &ret[1]; let result_r = &ret[2]; - assert_mr_eq!(rc_ptr, &rc_base.add_usize(4usize).unwrap()); + assert_mr_eq!(rc_ptr, &rc_base.add_usize(4).unwrap()); // Expected_q = q % PRIME (field element conversion). let expected_q = Felt252::from(&q); assert_mr_eq!(result_q, &expected_q); @@ -720,56 +720,56 @@ fn test_signed_div_rem( // Case: value=0x1234FCDA, n=10, base=16, bound=16, expected_output=vec![0xA, 0xD, // 0xC, 0xF, 0x4, 0x3, 0x2, 0x1, 0, 0] Expected: Success. #[case::hex_digits( - 0x1234FCDA_i64, - 10_i64, - 16_i64, - 16_i64, + 0x1234FCDA, + 10, + 16, + 16, Some(vec![0xA, 0xD, 0xC, 0xF, 0x4, 0x3, 0x2, 0x1, 0, 0]), expect_ok )] // Case: value=0x1234FCDA, n=10, base=256, bound=256, expected_output=vec![0xDA, // 0xFC, 0x34, 0x12, 0, 0, 0, 0, 0, 0] Expected: Success. #[case::byte_pairs( - 0x1234FCDA_i64, - 10_i64, - 256_i64, - 256_i64, + 0x1234FCDA, + 10, + 256, + 256, Some(vec![0xDA, 0xFC, 0x34, 0x12, 0, 0, 0, 0, 0, 0]), expect_ok )] // Case: value=0x1234FCDA, n=10, base=16, bound=15, expected_output=random // Expected: Error. #[case::out_of_bound_limb( - 0x1234FCDA_i64, - 10_i64, - 16_i64, - 15_i64, + 0x1234FCDA, + 10, + 16, + 15, None, expect_split_int_limb_out_of_range )] // Case: value=0xAAA, n=3, base=16, bound=11, expected_output=vec![0xA, 0xA, 0xA] // Expected: Success. #[case::exact_fit( - 0xAAA_i64, - 3_i64, - 16_i64, - 11_i64, + 0xAAA, + 3, + 16, + 11, Some(vec![0xA, 0xA, 0xA]), expect_ok )] // Case: value=0xAAA, n=3, base=16, bound=10, expected_output=random // Expected: Error. #[case::bound_too_small( - 0xAAA_i64, - 3_i64, - 16_i64, - 10_i64, + 0xAAA, + 3, + 16, + 10, None, expect_split_int_limb_out_of_range )] // Case: value=0xAAA, n=2, base=16, bound=16, expected_output=random // Expected: Error. -#[case::value_out_of_range(0xAAA_i64, 2_i64, 16_i64, 16_i64, None, expect_split_int_not_zero)] +#[case::value_out_of_range(0xAAA, 2, 16, 16, None, expect_split_int_not_zero)] fn test_split_int( mut runner: CairoFunctionRunner, #[case] value: i64, @@ -794,7 +794,7 @@ fn test_split_int( let expected_output = expected_output.expect("expected_output must be set for success case"); let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(2usize * n as usize).unwrap()); + assert_mr_eq!(&ret[0], &rc_base.add_usize(2 * n as usize).unwrap()); let range = runner.vm.get_range(output, n as usize); assert_eq!( @@ -819,10 +819,10 @@ fn test_split_int( #[rstest] // Case: value=0 // Expected: Success. -#[case::zero(Some(BigUint::from(0u64)), expect_ok)] +#[case::zero(Some(BigUint::zero()), expect_ok)] // Case: value=1 // Expected: Success. -#[case::one(Some(BigUint::from(1u64)), expect_ok)] +#[case::one(Some(BigUint::one()), expect_ok)] // Case: value=2 // Expected: Success. #[case::two(Some(BigUint::from(2u64)), expect_ok)] @@ -869,7 +869,7 @@ fn test_sqrt( ) { let value = value.unwrap_or_else(|| { let mut rng = thread_rng(); - let upper = BigUint::one() << 250usize; + let upper = BigUint::from(2u64).pow(250); rng.gen_biguint_range(&BigUint::zero(), &upper) }); @@ -885,7 +885,7 @@ fn test_sqrt( let ret = runner.vm.get_return_values(2).unwrap(); assert_mr_eq!( &ret[0], - &rc_base.add_usize(4usize).unwrap(), + &rc_base.add_usize(4).unwrap(), "range_check_ptr mismatch for sqrt({value})" ); @@ -975,12 +975,12 @@ fn test_is_quad_residue(mut runner: CairoFunctionRunner, #[case] x: Option Date: Sun, 29 Mar 2026 14:37:51 +0300 Subject: [PATCH 23/31] fix: remove assert_not_nullptr import not available in cairo-lang 0.13.5 Co-Authored-By: Claude Sonnet 4.6 --- vm/src/tests/cairo_test_suite/test_math/math_test.cairo | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/math_test.cairo b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo index 38e1c37e05..2584adc230 100644 --- a/vm/src/tests/cairo_test_suite/test_math/math_test.cairo +++ b/vm/src/tests/cairo_test_suite/test_math/math_test.cairo @@ -2,7 +2,6 @@ from starkware.cairo.common.math import ( assert_not_zero, - assert_not_nullptr, assert_not_equal, assert_nn, assert_le, From 78fdc0ab1b859cab2cdd37e6ff5884fb5c29c821 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 16:00:31 +0300 Subject: [PATCH 24/31] test(math_test_utils): add unit tests for is_quad_residue_mod_prime Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/math_test_utils.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs index aa79815475..b7caa944ec 100644 --- a/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs +++ b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs @@ -18,3 +18,21 @@ pub fn is_quad_residue_mod_prime(a: &BigUint) -> i64 { Err(_) => -1, } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Returns 1 for a known quadratic residue: 4 = 2² mod CAIRO_PRIME. + #[test] + fn is_quad_residue_mod_prime_returns_1_for_residue() { + assert_eq!(is_quad_residue_mod_prime(&BigUint::from(4u32)), 1); + } + + /// Returns 0 for a known non-residue: 3 is not a square mod CAIRO_PRIME. + #[test] + fn is_quad_residue_mod_prime_returns_0_for_non_residue() { + assert_eq!(is_quad_residue_mod_prime(&BigUint::from(3u32)), 0); + } + +} From db0283d5b4fb2743ceed7af73f079d576baee7c3 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 16:13:29 +0300 Subject: [PATCH 25/31] style: cargo fmt Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/math_test_utils.rs | 1 - .../test_math/test_math_cairo.rs | 24 ++++--------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs index b7caa944ec..df90cfbd49 100644 --- a/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs +++ b/vm/src/tests/cairo_test_suite/test_math/math_test_utils.rs @@ -34,5 +34,4 @@ mod tests { fn is_quad_residue_mod_prime_returns_0_for_non_residue() { assert_eq!(is_quad_residue_mod_prime(&BigUint::from(3u32)), 0); } - } diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index dafaf5ba6b..f5609a8f16 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -3,6 +3,9 @@ use std::sync::LazyLock; use super::math_test_utils::{is_quad_residue_mod_prime, MAX_DIV, RC_BOUND}; +use crate::assert_mr_eq; +use crate::cairo_args; +use crate::load_cairo_program; use crate::test_helpers::error_utils::{ expect_assert_lt_felt252, expect_assert_not_equal_fail, expect_diff_index_comp, expect_diff_type_comparison, expect_hint_assert_not_zero, expect_hint_out_of_valid_range, @@ -10,9 +13,6 @@ use crate::test_helpers::error_utils::{ expect_non_le_felt252, expect_ok, expect_split_int_limb_out_of_range, expect_split_int_not_zero, VmCheck, }; -use crate::assert_mr_eq; -use crate::cairo_args; -use crate::load_cairo_program; use crate::types::builtin_name::BuiltinName; use crate::types::program::Program; use crate::types::relocatable::MaybeRelocatable; @@ -739,14 +739,7 @@ fn test_signed_div_rem( )] // Case: value=0x1234FCDA, n=10, base=16, bound=15, expected_output=random // Expected: Error. -#[case::out_of_bound_limb( - 0x1234FCDA, - 10, - 16, - 15, - None, - expect_split_int_limb_out_of_range -)] +#[case::out_of_bound_limb(0x1234FCDA, 10, 16, 15, None, expect_split_int_limb_out_of_range)] // Case: value=0xAAA, n=3, base=16, bound=11, expected_output=vec![0xA, 0xA, 0xA] // Expected: Success. #[case::exact_fit( @@ -759,14 +752,7 @@ fn test_signed_div_rem( )] // Case: value=0xAAA, n=3, base=16, bound=10, expected_output=random // Expected: Error. -#[case::bound_too_small( - 0xAAA, - 3, - 16, - 10, - None, - expect_split_int_limb_out_of_range -)] +#[case::bound_too_small(0xAAA, 3, 16, 10, None, expect_split_int_limb_out_of_range)] // Case: value=0xAAA, n=2, base=16, bound=16, expected_output=random // Expected: Error. #[case::value_out_of_range(0xAAA, 2, 16, 16, None, expect_split_int_not_zero)] From fc935bc0f1fdcc0fcac37ecd2f638cd4369bb3b5 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Sun, 29 Mar 2026 17:19:32 +0300 Subject: [PATCH 26/31] chore: update CHANGELOG for PR #2379 Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25bcc8ac3a..ca96889f55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq`, `load_cairo_program!` macro and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) * feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) +* feat: add math Cairo test suite under `vm/src/tests/cairo_test_suite` using the `function_runner` feature flag [#2379](https://github.com/starkware-libs/cairo-vm/pull/2379) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) From 8c01849e5c16a346ea4bb2accbd5031114710d51 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 12:51:08 +0300 Subject: [PATCH 27/31] refactor: replace function_runner cfg gates with test_utils in cairo_test_suite Also fix duplicate and stale CHANGELOG entries for PRs #2377-#2379. Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 5 ++++- vm/src/tests/cairo_test_suite/mod.rs | 2 +- vm/src/tests/mod.rs | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca96889f55..d39c2daab9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,11 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. #### Upcoming Changes * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq`, `load_cairo_program!` macro and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) +* refactor: add `CairoFunctionRunner` type alias for `CairoRunner` under the `test_utils` feature flag [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) +* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) + * feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) -* feat: add math Cairo test suite under `vm/src/tests/cairo_test_suite` using the `function_runner` feature flag [#2379](https://github.com/starkware-libs/cairo-vm/pull/2379) +* feat: add math Cairo test suite under `vm/src/tests/cairo_test_suite` using the `test_utils` feature flag [#2379](https://github.com/starkware-libs/cairo-vm/pull/2379) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) diff --git a/vm/src/tests/cairo_test_suite/mod.rs b/vm/src/tests/cairo_test_suite/mod.rs index 110584fc80..8007a32d0f 100644 --- a/vm/src/tests/cairo_test_suite/mod.rs +++ b/vm/src/tests/cairo_test_suite/mod.rs @@ -1,2 +1,2 @@ -#[cfg(feature = "function_runner")] +#[cfg(feature = "test_utils")] mod test_math; diff --git a/vm/src/tests/mod.rs b/vm/src/tests/mod.rs index 7648629512..d222c777d7 100644 --- a/vm/src/tests/mod.rs +++ b/vm/src/tests/mod.rs @@ -36,7 +36,7 @@ mod cairo_pie_test; #[cfg(feature = "test_utils")] mod skip_instruction_test; -#[cfg(feature = "function_runner")] +#[cfg(feature = "test_utils")] mod cairo_test_suite; //For simple programs that should just succeed and have no special needs. From 454d39372625bb83376b83fab8bb1f44399cd740 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Mon, 6 Apr 2026 23:25:25 +0300 Subject: [PATCH 28/31] fix: replace CairoFunctionRunner with CairoRunner in test_math_cairo CairoFunctionRunner type alias was removed, use CairoRunner directly. Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/test_math_cairo.rs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index f5609a8f16..e0ce6743d4 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -17,7 +17,7 @@ use crate::types::builtin_name::BuiltinName; use crate::types::program::Program; use crate::types::relocatable::MaybeRelocatable; use crate::utils::CAIRO_PRIME; -use crate::vm::runners::function_runner::CairoFunctionRunner; +use crate::vm::runners::cairo_runner::CairoRunner; use crate::Felt252; use num_bigint::{BigInt, BigUint, RandBigInt}; use num_traits::{One, Signed, Zero}; @@ -56,10 +56,10 @@ static INTERESTING_FELTS: LazyLock> = LazyLock::new(|| { // ===================== Fixture ===================== -/// Creates a fresh CairoFunctionRunner from the shared PROGRAM. +/// Creates a fresh CairoRunner from the shared PROGRAM. #[fixture] -fn runner() -> CairoFunctionRunner { - CairoFunctionRunner::new_for_testing(&PROGRAM).unwrap() +fn runner() -> CairoRunner { + CairoRunner::new_for_testing(&PROGRAM).unwrap() } // ===================== test_assert_not_zero ===================== @@ -174,7 +174,7 @@ fn test_assert_not_equal( // Expected: Error. #[case::near_prime(&*CAIRO_PRIME - BigUint::one(), expect_hint_value_outside_250_bit_range)] fn test_assert_250_bit( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] value: BigUint, #[case] check: VmCheck<()>, ) { @@ -244,7 +244,7 @@ fn test_assert_250_bit( // Case: idx=15 // Expected: Success. #[case::idx_15(15)] -fn test_split_felt(mut runner: CairoFunctionRunner, #[case] idx: usize) { +fn test_split_felt(mut runner: CairoRunner, #[case] idx: usize) { let mask_128 = BigUint::from(2u64).pow(128) - BigUint::one(); let value = &INTERESTING_FELTS[idx]; @@ -275,7 +275,7 @@ fn test_split_felt(mut runner: CairoFunctionRunner, #[case] idx: usize) { #[rstest] fn test_assert_le_felt( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx0: usize, #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx1: usize, ) { @@ -308,7 +308,7 @@ fn test_assert_le_felt( #[rstest] fn test_assert_lt_felt( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx0: usize, #[values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)] idx1: usize, ) { @@ -356,7 +356,7 @@ fn test_assert_lt_felt( // Expected: Error. #[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] fn test_abs_value( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: VmCheck<()>, ) { @@ -399,7 +399,7 @@ fn test_abs_value( // Expected: Error. #[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] fn test_sign( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: VmCheck<()>, ) { @@ -501,7 +501,7 @@ fn test_sign( expect_hint_out_of_valid_range )] fn test_unsigned_div_rem( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] q: Option, #[case] div: Option, #[case] r: Option, @@ -644,7 +644,7 @@ fn test_unsigned_div_rem( expect_hint_out_of_valid_range )] fn test_signed_div_rem( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] q: Option, #[case] div: Option, #[case] r: Option, @@ -757,7 +757,7 @@ fn test_signed_div_rem( // Expected: Error. #[case::value_out_of_range(0xAAA, 2, 16, 16, None, expect_split_int_not_zero)] fn test_split_int( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] value: i64, #[case] n: i64, #[case] base: i64, @@ -849,7 +849,7 @@ fn test_split_int( expect_hint_value_outside_250_bit_range )] fn test_sqrt( - mut runner: CairoFunctionRunner, + mut runner: CairoRunner, #[case] value: Option, #[case] check: VmCheck<()>, ) { @@ -893,7 +893,7 @@ fn test_sqrt( // Case: n=16 // Expected: Success. #[case::sixteen_coefficients(16)] -fn test_horner_eval(mut runner: CairoFunctionRunner, #[case] n: usize) { +fn test_horner_eval(mut runner: CairoRunner, #[case] n: usize) { let mut rng = thread_rng(); let prime = &*CAIRO_PRIME; @@ -931,7 +931,7 @@ fn test_horner_eval(mut runner: CairoFunctionRunner, #[case] n: usize) { // Case: x=random // Expected: Success. #[case::random(None)] -fn test_is_quad_residue(mut runner: CairoFunctionRunner, #[case] x: Option) { +fn test_is_quad_residue(mut runner: CairoRunner, #[case] x: Option) { let prime = &*CAIRO_PRIME; let x = x.unwrap_or_else(|| { @@ -953,7 +953,7 @@ fn test_is_quad_residue(mut runner: CairoFunctionRunner, #[case] x: Option Date: Mon, 6 Apr 2026 23:30:01 +0300 Subject: [PATCH 29/31] update CHANGELOG.md --- CHANGELOG.md | 4 +--- .../test_math/test_math_cairo.rs | 18 +++--------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d39c2daab9..f30a924b19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,10 +13,8 @@ Both branches support Stwo prover opcodes (Blake2s, QM31) since v2.0.0. #### Upcoming Changes * feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq`, `load_cairo_program!` macro and `expect_*` error checkers, behind `test_utils` feature flag [#2381](https://github.com/starkware-libs/cairo-vm/pull/2381) -* refactor: add `CairoFunctionRunner` type alias for `CairoRunner` under the `test_utils` feature flag [#2377](https://github.com/starkware-libs/cairo-vm/pull/2377) -* feat: add `test_helpers` module (`error_utils`, `test_utils`) with `assert_mr_eq!`, `load_cairo_program!` macros and `expect_*` error checkers, behind `test_utils` feature flag [#2378](https://github.com/starkware-libs/cairo-vm/pull/2378) - * feat(makefile,ci): add `cairo_test_suite_programs` Makefile target and CI integration to compile Cairo test suite programs before running tests [#2380](https://github.com/starkware-libs/cairo-vm/pull/2380) + * feat: add math Cairo test suite under `vm/src/tests/cairo_test_suite` using the `test_utils` feature flag [#2379](https://github.com/starkware-libs/cairo-vm/pull/2379) * Add Stwo cairo runner API [#2351](https://github.com/lambdaclass/cairo-vm/pull/2351) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index e0ce6743d4..5b91d7c746 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -355,11 +355,7 @@ fn test_assert_lt_felt( // Case: value_case=-RC_BOUND // Expected: Error. #[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] -fn test_abs_value( - mut runner: CairoRunner, - #[case] value_case: BigInt, - #[case] check: VmCheck<()>, -) { +fn test_abs_value(mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: VmCheck<()>) { let rc_base = runner .get_builtin_base(BuiltinName::range_check) .expect("range_check builtin not found"); @@ -398,11 +394,7 @@ fn test_abs_value( // Case: value_case=-RC_BOUND // Expected: Error. #[case(-BigInt::from(RC_BOUND.clone()), expect_hint_value_outside_valid_range)] -fn test_sign( - mut runner: CairoRunner, - #[case] value_case: BigInt, - #[case] check: VmCheck<()>, -) { +fn test_sign(mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: VmCheck<()>) { let rc_base = runner .get_builtin_base(BuiltinName::range_check) .expect("range_check builtin not found"); @@ -848,11 +840,7 @@ fn test_split_int( Some(&*CAIRO_PRIME - BigUint::one()), expect_hint_value_outside_250_bit_range )] -fn test_sqrt( - mut runner: CairoRunner, - #[case] value: Option, - #[case] check: VmCheck<()>, -) { +fn test_sqrt(mut runner: CairoRunner, #[case] value: Option, #[case] check: VmCheck<()>) { let value = value.unwrap_or_else(|| { let mut rng = thread_rng(); let upper = BigUint::from(2u64).pow(250); From fa68c824d49f17058c07cd0b2ad7b3f27af817b2 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 9 Apr 2026 12:22:42 +0300 Subject: [PATCH 30/31] refactor: migrate assert_mr_eq! macro calls to function in test suite Co-Authored-By: Claude Sonnet 4.6 --- .../test_math/test_math_cairo.rs | 84 ++++++------------- 1 file changed, 24 insertions(+), 60 deletions(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index 5b91d7c746..04cabda94e 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -3,7 +3,7 @@ use std::sync::LazyLock; use super::math_test_utils::{is_quad_residue_mod_prime, MAX_DIV, RC_BOUND}; -use crate::assert_mr_eq; +use crate::test_helpers::test_utils::assert_mr_eq; use crate::cairo_args; use crate::load_cairo_program; use crate::test_helpers::error_utils::{ @@ -189,7 +189,7 @@ fn test_assert_250_bit( // If successful, verify the return value if res.is_ok() { let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(3).unwrap()); + assert_mr_eq(&ret[0], &rc_base.add_usize(3).unwrap()); } } @@ -262,13 +262,9 @@ fn test_split_felt(mut runner: CairoRunner, #[case] idx: usize) { let ret = runner.vm.get_return_values(3).unwrap(); // ret = [range_check_ptr, high, low] - assert_mr_eq!( - &ret[0], - &rc_base.add_usize(3).unwrap(), - "range_check_ptr mismatch for value {value}" - ); - assert_mr_eq!(&ret[1], &expected_high, "high mismatch for value {value}"); - assert_mr_eq!(&ret[2], &expected_low, "low mismatch for value {value}"); + assert_mr_eq(&ret[0], &rc_base.add_usize(3).unwrap()); + assert_mr_eq(&ret[1], &expected_high); + assert_mr_eq(&ret[2], &expected_low); } // ===================== test_assert_le_felt ===================== @@ -293,11 +289,7 @@ fn test_assert_le_felt( .run_default_cairo0("assert_le_felt", &args) .unwrap_or_else(|e| panic!("assert_le_felt failed for {value0} <= {value1}: {e}")); let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!( - &ret[0], - &rc_base.add_usize(4).unwrap(), - "range_check_ptr mismatch for {value0} <= {value1}" - ); + assert_mr_eq(&ret[0], &rc_base.add_usize(4).unwrap()); } else { let result = runner.run_default_cairo0("assert_le_felt", &args); expect_non_le_felt252(&result); @@ -326,11 +318,7 @@ fn test_assert_lt_felt( .run_default_cairo0("assert_lt_felt", &args) .unwrap_or_else(|e| panic!("assert_lt_felt failed for {value0} < {value1}: {e}")); let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!( - &ret[0], - &rc_base.add_usize(4).unwrap(), - "range_check_ptr mismatch for {value0} < {value1}" - ); + assert_mr_eq(&ret[0], &rc_base.add_usize(4).unwrap()); } else { let result = runner.run_default_cairo0("assert_lt_felt", &args); expect_assert_lt_felt252(&result); @@ -372,8 +360,8 @@ fn test_abs_value(mut runner: CairoRunner, #[case] value_case: BigInt, #[case] c let abs_value = value_case.magnitude(); if abs_value < &rc_bound_biguint { let ret = runner.vm.get_return_values(2).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(1).unwrap()); - assert_mr_eq!(&ret[1], abs_value); + assert_mr_eq(&ret[0], &rc_base.add_usize(1).unwrap()); + assert_mr_eq(&ret[1], abs_value); } } @@ -417,7 +405,7 @@ fn test_sign(mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: } else { rc_base.add_usize(1).unwrap() }; - assert_mr_eq!(&ret[0], &expected_rc_ptr); + assert_mr_eq(&ret[0], &expected_rc_ptr); // res == (0 if value == 0 else 1 if value > 0 else PRIME - 1) let expected_sign = if value_case.is_zero() { @@ -427,7 +415,7 @@ fn test_sign(mut runner: CairoRunner, #[case] value_case: BigInt, #[case] check: } else { &*CAIRO_PRIME - BigUint::one() }; - assert_mr_eq!(&ret[1], &expected_sign); + assert_mr_eq(&ret[1], &expected_sign); } } @@ -542,13 +530,9 @@ fn test_unsigned_div_rem( // If successful, verify the results match expected values if result.is_ok() { let ret = runner.vm.get_return_values(3).unwrap(); - assert_mr_eq!( - &ret[0], - &rc_base.add_usize(3).unwrap(), - "range_check_ptr mismatch" - ); - assert_mr_eq!(&ret[1], &q, "quotient mismatch"); - assert_mr_eq!(&ret[2], &r, "remainder mismatch"); + assert_mr_eq(&ret[0], &rc_base.add_usize(3).unwrap()); + assert_mr_eq(&ret[1], &q); + assert_mr_eq(&ret[2], &r); } } @@ -699,11 +683,11 @@ fn test_signed_div_rem( let result_q = &ret[1]; let result_r = &ret[2]; - assert_mr_eq!(rc_ptr, &rc_base.add_usize(4).unwrap()); + assert_mr_eq(rc_ptr, &rc_base.add_usize(4).unwrap()); // Expected_q = q % PRIME (field element conversion). let expected_q = Felt252::from(&q); - assert_mr_eq!(result_q, &expected_q); - assert_mr_eq!(result_r, &r); + assert_mr_eq(result_q, &expected_q); + assert_mr_eq(result_r, &r); } } @@ -772,7 +756,7 @@ fn test_split_int( let expected_output = expected_output.expect("expected_output must be set for success case"); let ret = runner.vm.get_return_values(1).unwrap(); - assert_mr_eq!(&ret[0], &rc_base.add_usize(2 * n as usize).unwrap()); + assert_mr_eq(&ret[0], &rc_base.add_usize(2 * n as usize).unwrap()); let range = runner.vm.get_range(output, n as usize); assert_eq!( @@ -784,11 +768,7 @@ fn test_split_int( let actual_val = actual .as_ref() .unwrap_or_else(|| panic!("Missing output at index {i}")); - assert_mr_eq!( - actual_val.as_ref(), - *exp, - "split_int output mismatch at index {i}" - ); + assert_mr_eq(actual_val.as_ref(), *exp); } } } @@ -857,18 +837,10 @@ fn test_sqrt(mut runner: CairoRunner, #[case] value: Option, #[case] ch if result.is_ok() { let ret = runner.vm.get_return_values(2).unwrap(); - assert_mr_eq!( - &ret[0], - &rc_base.add_usize(4).unwrap(), - "range_check_ptr mismatch for sqrt({value})" - ); + assert_mr_eq(&ret[0], &rc_base.add_usize(4).unwrap()); let expected_root = value.sqrt(); - assert_mr_eq!( - &ret[1], - &expected_root, - "sqrt result mismatch for value={value}" - ); + assert_mr_eq(&ret[1], &expected_root); } } @@ -907,7 +879,7 @@ fn test_horner_eval(mut runner: CairoRunner, #[case] n: usize) { .map(|(i, coef)| coef * point.modpow(&BigUint::from(i), prime)) .fold(BigUint::zero(), |acc, x| (acc + x) % prime); - assert_mr_eq!(&ret[0], &expected); + assert_mr_eq(&ret[0], &expected); } // ===================== test_is_quad_residue ===================== @@ -933,11 +905,7 @@ fn test_is_quad_residue(mut runner: CairoRunner, #[case] x: Option) { let ret = runner.vm.get_return_values(1).unwrap(); let expected = is_quad_residue_mod_prime(&x); - assert_mr_eq!( - &ret[0], - expected, - "is_quad_residue({x}) should return {expected}" - ); + assert_mr_eq(&ret[0], expected); // Test is_quad_residue(3 * x) // 3 is not a quadratic residue modulo PRIME @@ -956,9 +924,5 @@ fn test_is_quad_residue(mut runner: CairoRunner, #[case] x: Option) { } else { 1 // x is not QR, 3 is not QR, so 3*x is QR (product of two non-QR is QR) }; - assert_mr_eq!( - &ret2[0], - expected2, - "is_quad_residue(3 * {x}) should return {expected2}" - ); + assert_mr_eq(&ret2[0], expected2); } From d6a6c58603e52ee73e88e2c861febd017bf2df40 Mon Sep 17 00:00:00 2001 From: "Naor.d" Date: Thu, 9 Apr 2026 12:24:53 +0300 Subject: [PATCH 31/31] chore: apply cargo fmt Co-Authored-By: Claude Sonnet 4.6 --- vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs index 04cabda94e..8789960301 100644 --- a/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs +++ b/vm/src/tests/cairo_test_suite/test_math/test_math_cairo.rs @@ -3,7 +3,6 @@ use std::sync::LazyLock; use super::math_test_utils::{is_quad_residue_mod_prime, MAX_DIV, RC_BOUND}; -use crate::test_helpers::test_utils::assert_mr_eq; use crate::cairo_args; use crate::load_cairo_program; use crate::test_helpers::error_utils::{ @@ -13,6 +12,7 @@ use crate::test_helpers::error_utils::{ expect_non_le_felt252, expect_ok, expect_split_int_limb_out_of_range, expect_split_int_not_zero, VmCheck, }; +use crate::test_helpers::test_utils::assert_mr_eq; use crate::types::builtin_name::BuiltinName; use crate::types::program::Program; use crate::types::relocatable::MaybeRelocatable;