Skip to content

Commit 44b56da

Browse files
(test): Adding casm trace to e2e tests
1 parent a624220 commit 44b56da

File tree

2 files changed

+551
-1
lines changed

2 files changed

+551
-1
lines changed

tests/e2e_test.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cairo_lang_filesystem::flag::{Flag, FlagsGroup};
77
use cairo_lang_filesystem::ids::FlagLongId;
88
use cairo_lang_lowering::db::lowering_group_input;
99
use cairo_lang_lowering::optimizations::config::{OptimizationConfig, Optimizations};
10+
use cairo_lang_runner::{Arg, RunResultValue, SierraCasmRunner};
1011
use cairo_lang_semantic::test_utils::setup_test_module;
1112
use cairo_lang_sierra::extensions::gas::{CostTokenMap, CostTokenType};
1213
use cairo_lang_sierra::ids::FunctionId;
@@ -24,6 +25,7 @@ use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
2425
use cairo_lang_utils::unordered_hash_map::UnorderedHashMap;
2526
use itertools::Itertools;
2627
use 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`.
215266
struct 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

Comments
 (0)