Skip to content

Commit

Permalink
feat: should_panic attribute (#399)
Browse files Browse the repository at this point in the history
<!-- Reference any GitHub issues resolved by this PR -->

Closes #355 

## Introduced changes

<!-- A brief description of the changes -->

- Adds support for a should_panic macro. The runner checks whether the
panicked data is the one expected.
- Added tests.

## Breaking changes

<!-- List of all breaking changes, if applicable -->

## Checklist

<!-- Make sure all of these are complete -->

- [x] Linked relevant issue
- [x] Updated relevant documentation
- [x] Added relevant tests
- [x] Performed self-review of the code
- [x] Added changes to `CHANGELOG.md`

---------

Co-authored-by: Piotr Magiera <[email protected]>
Co-authored-by: Piotr Magiera <[email protected]>
  • Loading branch information
3 people authored Aug 5, 2023
1 parent 99206c7 commit c693788
Show file tree
Hide file tree
Showing 11 changed files with 306 additions and 45 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

#### Added

- `#[should_panic]` attribute support
- Documentation to public methods

#### Changed
Expand Down Expand Up @@ -91,4 +92,4 @@ cheatcodes = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "0.

#### Added

- Initial release
- Initial release
35 changes: 35 additions & 0 deletions crates/forge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ fn strip_path_from_test_names(test_cases: Vec<TestCase>) -> Result<Vec<TestCase>
Ok(TestCase {
name,
available_gas: test_case.available_gas,
expected_result: test_case.expected_result,
})
})
.collect()
Expand Down Expand Up @@ -381,6 +382,7 @@ fn test_name_contains(test_name_filter: &str, test: &TestCase) -> Result<bool> {
mod tests {
use super::*;
use assert_fs::fixture::PathCopy;
use test_collector::ExpectedTestResult;

#[test]
fn collecting_tests() {
Expand Down Expand Up @@ -412,14 +414,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "outer::crate2::execute_next_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
];

Expand All @@ -429,6 +434,7 @@ mod tests {
vec![TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},]
);

Expand All @@ -438,6 +444,7 @@ mod tests {
vec![TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},]
);

Expand All @@ -448,14 +455,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "outer::crate2::execute_next_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
]
);
Expand All @@ -470,14 +480,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "outer::crate2::execute_next_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
]
);
Expand All @@ -489,14 +502,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "outer::crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
];

Expand All @@ -510,18 +526,22 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "outer::crate3::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
];

Expand All @@ -537,6 +557,7 @@ mod tests {
vec![TestCase {
name: "do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},]
);

Expand All @@ -547,6 +568,7 @@ mod tests {
vec![TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},]
);

Expand All @@ -561,6 +583,7 @@ mod tests {
vec![TestCase {
name: "outer::crate3::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},]
);
}
Expand All @@ -571,14 +594,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
];

Expand All @@ -589,14 +615,17 @@ mod tests {
TestCase {
name: "crate1::do_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
]
);
Expand All @@ -608,14 +637,17 @@ mod tests {
TestCase {
name: "/Users/user/forge/tests/data/simple_package/src::test::test_fib".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "src/crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
];

Expand All @@ -626,14 +658,17 @@ mod tests {
TestCase {
name: "src::test::test_fib".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
TestCase {
name: "crate2::run_other_thing".to_string(),
available_gas: None,
expected_result: ExpectedTestResult::Success,
},
]
);
Expand Down
100 changes: 84 additions & 16 deletions crates/forge/src/test_case_summary.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use cairo_felt::Felt252;
use cairo_lang_runner::short_string::as_cairo_short_string;
use cairo_lang_runner::{RunResult, RunResultValue};
use std::option::Option;
use test_collector::TestCase;
use test_collector::{ExpectedPanicValue, ExpectedTestResult, TestCase};

/// Summary of running a single test case
#[derive(Debug, PartialEq, Clone)]
Expand Down Expand Up @@ -34,16 +35,41 @@ pub enum TestCaseSummary {
impl TestCaseSummary {
#[must_use]
pub(crate) fn from_run_result(run_result: RunResult, test_case: &TestCase) -> Self {
match run_result.value {
RunResultValue::Success(_) => TestCaseSummary::Passed {
name: test_case.name.to_string(),
msg: extract_result_data(&run_result),
run_result,
let name = test_case.name.to_string();
let msg = extract_result_data(&run_result, &test_case.expected_result);
match run_result.clone().value {
RunResultValue::Success(_) => match &test_case.expected_result {
ExpectedTestResult::Success => TestCaseSummary::Passed {
name,
msg,
run_result,
},
ExpectedTestResult::Panics(_) => TestCaseSummary::Failed {
name,
msg,
run_result: Some(run_result),
},
},
RunResultValue::Panic(_) => TestCaseSummary::Failed {
name: test_case.name.to_string(),
msg: extract_result_data(&run_result),
run_result: Some(run_result),
RunResultValue::Panic(value) => match &test_case.expected_result {
ExpectedTestResult::Success => TestCaseSummary::Failed {
name,
msg,
run_result: Some(run_result),
},
ExpectedTestResult::Panics(panic_expectation) => match panic_expectation {
ExpectedPanicValue::Exact(expected) if &value != expected => {
TestCaseSummary::Failed {
name,
msg,
run_result: Some(run_result),
}
}
_ => TestCaseSummary::Passed {
name,
msg,
run_result,
},
},
},
}
}
Expand All @@ -56,12 +82,8 @@ impl TestCaseSummary {
}
}

#[must_use]
fn extract_result_data(run_result: &RunResult) -> Option<String> {
let data = match &run_result.value {
RunResultValue::Panic(data) | RunResultValue::Success(data) => data,
};

/// Helper function to build `readable_text` from a run data.
fn build_readable_text(data: &Vec<Felt252>) -> Option<String> {
let mut readable_text = String::new();

for felt in data {
Expand All @@ -78,3 +100,49 @@ fn extract_result_data(run_result: &RunResult) -> Option<String> {
Some(readable_text)
}
}

#[must_use]
/// Returns a string with the data that was produced by the test case.
/// If the test was expected to fail with specific data e.g. `#[should_panic(expected: ('data',))]`
/// and failed to do so, it returns a string comparing the panic data and the expected data.
pub(crate) fn extract_result_data(
run_result: &RunResult,
expectation: &ExpectedTestResult,
) -> Option<String> {
match &run_result.value {
RunResultValue::Success(data) => build_readable_text(data),
RunResultValue::Panic(panic_data) => {
let expected_data = match expectation {
ExpectedTestResult::Panics(panic_expectation) => match panic_expectation {
ExpectedPanicValue::Exact(data) => Some(data),
ExpectedPanicValue::Any => None,
},
ExpectedTestResult::Success => None,
};

let panic_string: String = panic_data
.iter()
.map(|felt| as_cairo_short_string(felt).unwrap_or_default())
.collect::<Vec<String>>()
.join(", ");

match expected_data {
Some(expected) if expected == panic_data => None,
Some(expected) => {
let expected_string = expected
.iter()
.map(|felt| as_cairo_short_string(felt).unwrap_or_default())
.collect::<Vec<String>>()
.join(", ");

Some(format!(
"\n Incorrect panic data\n {}\n {}\n",
format_args!("Actual: {panic_data:?} ({panic_string})"),
format_args!("Expected: {expected:?} ({expected_string})")
))
}
None => build_readable_text(panic_data),
}
}
}
}
3 changes: 3 additions & 0 deletions crates/forge/tests/data/should_panic_test/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[package]
name = "should_panic_test"
version = "0.1.0"
1 change: 1 addition & 0 deletions crates/forge/tests/data/should_panic_test/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading

0 comments on commit c693788

Please sign in to comment.