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/CHANGELOG.md b/CHANGELOG.md index e3e8395764..28cf2b20cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ 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/lib.rs b/vm/src/lib.rs index ab2bbc2e5f..dd485104bc 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 = "test_utils")] +pub mod test_helpers; + // TODO: use `Felt` directly pub use starknet_types_core::felt::Felt as Felt252; 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 new file mode 100644 index 0000000000..e30c10a63a --- /dev/null +++ b/vm/src/test_helpers/error_utils.rs @@ -0,0 +1,420 @@ +//! 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(_))) + ); +} + +#[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. + #[allow(clippy::result_large_err)] + 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/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..4c40f15a10 --- /dev/null +++ b/vm/src/test_helpers/test_utils.rs @@ -0,0 +1,172 @@ +/// 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,ignore +/// 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 = 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`?" + ), + }; + + match $crate::types::program::Program::from_bytes(&bytes, None) { + Ok(p) => p, + Err(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 = 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)+); + }}; +} + +#[cfg(test)] +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: + /// ```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))); + } + + /// `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"); + } +} diff --git a/vm/src/vm/runners/function_runner.rs b/vm/src/vm/runners/function_runner.rs index c584105710..e5b19eb0ff 100644 --- a/vm/src/vm/runners/function_runner.rs +++ b/vm/src/vm/runners/function_runner.rs @@ -1,7 +1,7 @@ //! Function runner extension methods for [`CairoRunner`]. //! //! Provides a simplified API for executing individual Cairo 0 functions by name or PC. -//! Enabled by the `function_runner` feature flag. +//! Enabled by the `test_utils` feature flag. use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor; use crate::hint_processor::hint_processor_definition::HintProcessor;