#![allow(clippy::ignore_without_reason)] #![allow(missing_docs)]
use proptest::prelude::*;
use ruchy::frontend::parser::Parser;
fn arb_interpolated_string() -> impl Strategy<Value = String> {
prop_oneof![
Just("Hello, World!".to_string()),
prop::string::string_regex("[a-z][a-z0-9_]{0,5}")
.expect("valid var")
.prop_map(|var| format!("f\"Value: {{{var}}}\"")),
(
prop::string::string_regex("[a-z][a-z0-9_]{0,5}").expect("valid var 1"),
prop::string::string_regex("[a-z][a-z0-9_]{0,5}").expect("valid var 2"),
)
.prop_map(|(v1, v2)| format!("f\"{{{v1}}} and {{{v2}}}\"")),
Just("f\"{{escaped}}\"".to_string()),
prop::string::string_regex("[a-z][a-z0-9_]{0,5}")
.expect("valid var")
.prop_map(|var| format!("f\"Text {{{var}}} more text\"")),
]
}
fn arb_expr_in_braces() -> impl Strategy<Value = String> {
prop_oneof![
prop::string::string_regex("[a-z][a-z0-9_]{0,8}")
.expect("valid var")
.prop_map(|v| format!("{{{v}}}")),
Just("{1 + 2}".to_string()),
Just("{x.to_string()}".to_string()),
Just("{format(\"test\")}".to_string()),
]
}
proptest! {
#[test]
fn prop_parse_interpolation_never_panics(s in arb_interpolated_string()) {
let result = std::panic::catch_unwind(|| {
Parser::new(&s).parse()
});
prop_assert!(result.is_ok(), "Parser panicked on: {}", s);
}
#[test]
fn prop_interpolation_parsing_deterministic(s in arb_interpolated_string()) {
let result1 = Parser::new(&s).parse();
let result2 = Parser::new(&s).parse();
match (result1, result2) {
(Ok(_), Ok(_)) => prop_assert!(true),
(Err(e1), Err(e2)) => {
prop_assert_eq!(e1.to_string(), e2.to_string(),
"Error messages differ for: {}", s);
}
_ => prop_assert!(false, "Inconsistent results for: {}", s),
}
}
#[test]
fn prop_escaped_braces_preserved(text in "[a-z ]{1,20}") {
let s = format!("f\"{{{{{text}}}}}\"");
let result = Parser::new(&s).parse();
prop_assert!(result.is_ok() || result.is_err(),
"Parser should handle escaped braces: {}", s);
}
#[test]
fn prop_valid_expr_in_braces_parse(expr in arb_expr_in_braces()) {
let s = format!("f\"{expr}\"");
let result = Parser::new(&s).parse();
if let Err(e) = result {
let err_msg = e.to_string();
prop_assert!(!err_msg.is_empty(), "Error message should not be empty");
}
}
#[test]
fn prop_interpolation_count_matches(
var1 in prop::string::string_regex("[a-z][a-z0-9_]{0,5}").expect("valid var"),
var2 in prop::string::string_regex("[a-z][a-z0-9_]{0,5}").expect("valid var"),
) {
let s = format!("f\"{{{var1}}} text {{{var2}}}\"");
let result = Parser::new(&s).parse();
if let Ok(ast) = result {
let ast_str = format!("{ast:?}");
prop_assert!(
ast_str.contains(&var1) || ast_str.contains(&var2),
"Variables should be preserved in AST"
);
}
}
#[test]
fn prop_invalid_interpolation_clear_error(
invalid_char in "[^a-zA-Z0-9_{}()\\[\\].,;:'\" ]"
) {
let s = format!("f\"{{{invalid_char}}}\"");
let result = Parser::new(&s).parse();
if let Err(e) = result {
let err_msg = e.to_string();
prop_assert!(!err_msg.is_empty(), "Should have error message");
}
}
}
#[cfg(test)]
mod unit_tests {
use super::*;
#[test]
fn test_basic_fstring_parses() {
let test_cases = vec!["f\"Hello\"", "f\"Hello {name}\"", "f\"{x}\""];
for code in test_cases {
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Failed to parse: {code}");
}
}
#[test]
fn test_escaped_braces_parse() {
let test_cases = vec![
"f\"{{literal}}\"",
];
for code in test_cases {
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Failed to parse: {code}");
}
}
#[test]
fn test_multiple_interpolations_parse() {
let test_cases = vec!["f\"{a} and {b}\"", "f\"x={x}, y={y}\""];
for code in test_cases {
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Failed to parse: {code}");
}
}
#[test]
fn test_expressions_in_braces_parse() {
let test_cases = vec!["f\"{1 + 2}\"", "f\"{x * 2}\"", "f\"{obj.method()}\""];
for code in test_cases {
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Failed to parse: {code}");
}
}
#[test]
fn test_format_specifiers_parse() {
let test_cases = vec!["f\"{value:.2f}\"", "f\"{num:05d}\""];
for code in test_cases {
let result = Parser::new(code).parse();
assert!(result.is_ok(), "Failed to parse: {code}");
}
}
}