@@ -7,6 +7,7 @@ use cairo_lang_filesystem::flag::{Flag, FlagsGroup};
77use cairo_lang_filesystem:: ids:: FlagLongId ;
88use cairo_lang_lowering:: db:: lowering_group_input;
99use cairo_lang_lowering:: optimizations:: config:: { OptimizationConfig , Optimizations } ;
10+ use cairo_lang_runner:: { Arg , RunResultValue , SierraCasmRunner } ;
1011use cairo_lang_semantic:: test_utils:: setup_test_module;
1112use cairo_lang_sierra:: extensions:: gas:: { CostTokenMap , CostTokenType } ;
1213use cairo_lang_sierra:: ids:: FunctionId ;
@@ -24,6 +25,7 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
2425use cairo_lang_utils:: unordered_hash_map:: UnorderedHashMap ;
2526use itertools:: Itertools ;
2627use salsa:: Setter ;
28+ use starknet_types_core:: felt:: Felt as Felt252 ;
2729
2830/// Salsa databases configured to find the corelib, when reused by different tests should be able to
2931/// use the cached queries that rely on the corelib's code, which vastly reduces the tests runtime.
@@ -106,6 +108,7 @@ cairo_lang_test_utils::test_file_test_with_runner!(
106108 u512: "u512" ,
107109 u64 : "u64" ,
108110 u8 : "u8" ,
111+ casm_run_sanity: "casm_run_sanity" ,
109112 } ,
110113 SmallE2ETestRunner
111114) ;
@@ -160,7 +163,24 @@ impl TestFileRunner for SmallE2ETestRunner {
160163 args : & OrderedHashMap < String , String > ,
161164 ) -> TestRunnerResult {
162165 let future_sierra = args. get ( "future_sierra" ) . is_some_and ( |v| v == "true" ) ;
163- run_e2e_test ( inputs, E2eTestParams { future_sierra, ..E2eTestParams :: default ( ) } )
166+ let skip_gas = args. get ( "skip_gas" ) . is_some_and ( |v| v == "true" ) ;
167+
168+ let test_data = match TestData :: parse ( args) {
169+ Ok ( td) => td,
170+ Err ( e) => {
171+ return TestRunnerResult { outputs : OrderedHashMap :: default ( ) , error : Some ( e) } ;
172+ }
173+ } ;
174+
175+ run_e2e_test (
176+ inputs,
177+ E2eTestParams {
178+ future_sierra,
179+ test_data,
180+ add_withdraw_gas : !skip_gas,
181+ ..E2eTestParams :: default ( )
182+ } ,
183+ )
164184 }
165185}
166186
@@ -211,6 +231,37 @@ impl TestFileRunner for SmallE2ETestRunnerMetadataComputation {
211231 }
212232}
213233
234+ /// Represents test data for function execution: function_name(input)=output
235+ #[ derive( Clone , Debug ) ]
236+ struct TestData {
237+ function_name : String ,
238+ input : Felt252 ,
239+ expected_output : Felt252 ,
240+ }
241+
242+ impl TestData {
243+ /// Parses test_data. Expects three args: test_data_function, test_data_input, test_data_output.
244+ fn parse ( args : & OrderedHashMap < String , String > ) -> Result < Option < TestData > , String > {
245+ let Some ( function_name) = args. get ( "test_data_function" ) . cloned ( ) else {
246+ return Ok ( None ) ;
247+ } ;
248+ let Some ( ( input, output) ) = args. get ( "test_data_input" ) . zip ( args. get ( "test_data_output" ) )
249+ else {
250+ return Err ( "Given test_data_function both test_data_input and test_data_output must \
251+ be present as well."
252+ . to_string ( ) ) ;
253+ } ;
254+
255+ let input = Felt252 :: from_dec_str ( input)
256+ . map_err ( |e| format ! ( "Failed to parse input as a felt '{}': {}" , input, e) ) ?;
257+ let expected_output = Felt252 :: from_dec_str ( output) . map_err ( |e| {
258+ format ! ( "Failed to parse expected output as a felt '{}': {}" , output, e)
259+ } ) ?;
260+
261+ Ok ( Some ( TestData { function_name, input, expected_output } ) )
262+ }
263+ }
264+
214265/// Represents the parameters of `run_e2e_test`.
215266struct E2eTestParams {
216267 /// Argument for `run_e2e_test` that controls whether to set the `add_withdraw_gas` flag
@@ -226,6 +277,9 @@ struct E2eTestParams {
226277
227278 /// Argument for `run_e2e_test` that controls whether to enable the `future_sierra` flag.
228279 future_sierra : bool ,
280+
281+ /// Test data for function execution validation.
282+ test_data : Option < TestData > ,
229283}
230284
231285/// Implements default for `E2eTestParams`.
@@ -236,6 +290,7 @@ impl Default for E2eTestParams {
236290 metadata_computation : false ,
237291 skip_optimization_passes : true ,
238292 future_sierra : false ,
293+ test_data : None ,
239294 }
240295 }
241296}
@@ -321,9 +376,69 @@ fn run_e2e_test(
321376 res. insert ( "function_costs" . into ( ) , function_costs_str) ;
322377 }
323378
379+ // Handle test_data if specified.
380+ // This runs AFTER sierra/casm generation, so compilation outputs are always verified first.
381+ if let Some ( test_data) = & params. test_data {
382+ if let Err ( e) =
383+ run_and_validate_test_data ( sierra_program, test_data, params. add_withdraw_gas )
384+ {
385+ return TestRunnerResult { outputs : res, error : Some ( e) } ;
386+ }
387+ }
388+
324389 TestRunnerResult :: success ( res)
325390}
326391
392+ /// Runs the specified function and validates its output against expected values.
393+ fn run_and_validate_test_data (
394+ sierra_program : Program ,
395+ test_data : & TestData ,
396+ withdraw_gas : bool ,
397+ ) -> Result < ( ) , String > {
398+ let runner = SierraCasmRunner :: new (
399+ sierra_program,
400+ withdraw_gas. then ( Default :: default) ,
401+ Default :: default ( ) ,
402+ None ,
403+ )
404+ . expect ( "Failed setting up runner for test_data." ) ;
405+
406+ let func = runner
407+ . find_function ( & test_data. function_name )
408+ . map_err ( |e| format ! ( "Function '{}' not found: {}" , test_data. function_name, e) ) ?;
409+
410+ let result = runner
411+ . run_function_with_starknet_context (
412+ func,
413+ vec ! [ Arg :: Value ( test_data. input) ] ,
414+ withdraw_gas. then_some ( usize:: MAX ) ,
415+ Default :: default ( ) ,
416+ )
417+ . map_err ( |e| format ! ( "Failed running function '{}': {}" , test_data. function_name, e) ) ?;
418+
419+ match & result. value {
420+ RunResultValue :: Success ( values) => {
421+ let actual = values. first ( ) . ok_or_else ( || {
422+ format ! (
423+ "Function '{}' returned no values, expected {}" ,
424+ test_data. function_name, test_data. expected_output
425+ )
426+ } ) ?;
427+ if * actual != test_data. expected_output {
428+ return Err ( format ! (
429+ "Function '{}' output mismatch: expected {}, got {}" ,
430+ test_data. function_name, test_data. expected_output, actual
431+ ) ) ;
432+ }
433+ Ok ( ( ) )
434+ }
435+ RunResultValue :: Panic ( panic_data) => Err ( format ! (
436+ "Function '{}' panicked with data: {:?}" ,
437+ test_data. function_name, panic_data
438+ ) ) ,
439+ }
440+ }
441+
327442/// Parses the `enforced_costs` test argument. It should consist of lines of the form
328443/// <function_name> <cost>
329444/// Where `function_name` is the fully-qualified name of the function, and `cost` is the cost to
0 commit comments