#![allow(clippy::needless_range_loop)]
use adze_common::{FieldThenParams, NameValueExpr};
use proptest::prelude::*;
use quote::ToTokens;
use syn::Expr;
fn ident_strategy() -> impl Strategy<Value = String> {
prop::string::string_regex("[a-z][a-z0-9_]{0,10}")
.unwrap()
.prop_filter("must be valid ident", |s| {
!s.is_empty() && syn::parse_str::<syn::Ident>(s).is_ok()
})
}
fn type_name() -> impl Strategy<Value = &'static str> {
prop::sample::select(
&[
"i32", "u32", "i64", "u64", "f32", "f64", "bool", "char", "String", "usize",
][..],
)
}
fn safe_pattern_char() -> impl Strategy<Value = char> {
prop::char::range(' ', '~').prop_filter("no backslash or quote", |c| *c != '\\' && *c != '"')
}
fn safe_pattern_string(max_len: usize) -> impl Strategy<Value = String> {
prop::collection::vec(safe_pattern_char(), 1..=max_len)
.prop_map(|v| v.into_iter().collect::<String>())
}
fn simple_regex_pattern() -> impl Strategy<Value = &'static str> {
prop::sample::select(
&[
r"\d+",
r"\w+",
r"\s",
r"[a-z]+",
r"[A-Z][a-zA-Z0-9]*",
r"[0-9]+",
r"[-+*/]",
r"\d+\.\d+",
r"[_a-zA-Z][_a-zA-Z0-9]*",
r"0[xX][0-9a-fA-F]+",
][..],
)
}
fn special_char_pattern() -> impl Strategy<Value = &'static str> {
prop::sample::select(
&[
"+", "-", "*", "/", "(", ")", "{", "}", "[", "]", ".", ",", ";", ":", "!", "?", "@",
"#", "$", "%", "^", "&", "|", "~", "<", ">", "=",
][..],
)
}
fn extract_str_value(expr: &Expr) -> Option<String> {
if let Expr::Lit(lit) = expr
&& let syn::Lit::Str(s) = &lit.lit
{
return Some(s.value());
}
None
}
fn find_param<'a>(ftp: &'a FieldThenParams, key: &str) -> Option<&'a NameValueExpr> {
ftp.params.iter().find(|p| p.path == key)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(80))]
#[test]
fn text_param_key_preserved(ty in type_name(), val in safe_pattern_string(12)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "text").unwrap();
prop_assert_eq!(param.path.to_string(), "text");
}
#[test]
fn text_value_roundtrip(ty in type_name(), val in safe_pattern_string(12)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "text").unwrap();
let extracted = extract_str_value(¶m.expr).unwrap();
prop_assert_eq!(extracted, val);
}
#[test]
fn text_value_is_lit(ty in type_name(), val in safe_pattern_string(8)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "text").unwrap();
prop_assert!(matches!(param.expr, Expr::Lit(_)));
}
#[test]
fn text_tokens_contain_value(ty in type_name(), val in safe_pattern_string(8)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "text").unwrap();
let tokens = param.expr.to_token_stream().to_string();
prop_assert!(tokens.contains(&val));
}
#[test]
fn text_standalone_nve(val in safe_pattern_string(10)) {
let src = format!(r#"text = "{val}""#);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert_eq!(nve.path.to_string(), "text");
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, val);
}
#[test]
fn text_clone_preserves(val in safe_pattern_string(10)) {
let src = format!(r#"text = "{val}""#);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let cloned = nve.clone();
prop_assert_eq!(
extract_str_value(&nve.expr),
extract_str_value(&cloned.expr),
);
}
#[test]
fn text_length_preserved(ty in type_name(), val in safe_pattern_string(15)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert_eq!(extracted.len(), val.len());
}
#[test]
fn text_single_char(ty in type_name(), ch in safe_pattern_char()) {
let src = format!(r#"{ty}, text = "{ch}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert_eq!(extracted, ch.to_string());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(80))]
#[test]
fn pattern_param_key_preserved(ty in type_name(), pat in simple_regex_pattern()) {
let src = format!(r##"{ty}, pattern = r"{pat}""##);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "pattern").unwrap();
prop_assert_eq!(param.path.to_string(), "pattern");
}
#[test]
fn pattern_value_roundtrip(ty in type_name(), pat in simple_regex_pattern()) {
let src = format!(r##"{ty}, pattern = r"{pat}""##);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "pattern").unwrap();
let extracted = extract_str_value(¶m.expr).unwrap();
prop_assert_eq!(extracted, pat);
}
#[test]
fn pattern_standalone_nve(pat in simple_regex_pattern()) {
let src = format!(r##"pattern = r"{pat}""##);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert_eq!(nve.path.to_string(), "pattern");
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, pat);
}
#[test]
fn pattern_clone_preserves(pat in simple_regex_pattern()) {
let src = format!(r##"pattern = r"{pat}""##);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let cloned = nve.clone();
prop_assert_eq!(
extract_str_value(&nve.expr),
extract_str_value(&cloned.expr),
);
}
#[test]
fn pattern_is_str_lit(ty in type_name(), pat in simple_regex_pattern()) {
let src = format!(r##"{ty}, pattern = r"{pat}""##);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let param = find_param(&ftp, "pattern").unwrap();
prop_assert!(extract_str_value(¶m.expr).is_some());
}
#[test]
fn pattern_text_distinct_keys(ty in type_name(), pat in simple_regex_pattern(), txt in safe_pattern_string(6)) {
let src = format!(r##"{ty}, pattern = r"{pat}", text = "{txt}""##);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert!(find_param(&ftp, "pattern").is_some());
prop_assert!(find_param(&ftp, "text").is_some());
let pat_val = extract_str_value(&find_param(&ftp, "pattern").unwrap().expr).unwrap();
let txt_val = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert_eq!(pat_val, pat);
prop_assert_eq!(txt_val, txt);
}
#[test]
fn pattern_length_preserved(pat in simple_regex_pattern()) {
let src = format!(r##"pattern = r"{pat}""##);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted.len(), pat.len());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(60))]
#[test]
fn special_char_text_roundtrip(ty in type_name(), ch in special_char_pattern()) {
let src = format!(r#"{ty}, text = "{ch}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert_eq!(extracted, ch);
}
#[test]
fn special_char_is_lit(ch in special_char_pattern()) {
let src = format!(r#"text = "{ch}""#);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert!(matches!(nve.expr, Expr::Lit(_)));
}
#[test]
fn special_chars_concat(
ty in type_name(),
chars in prop::collection::vec(special_char_pattern(), 1..=5),
) {
let combined: String = chars.join("");
let src = format!(r#"{ty}, text = "{combined}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert_eq!(extracted, combined);
}
#[test]
fn special_char_clone(ch in special_char_pattern()) {
let src = format!(r#"text = "{ch}""#);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let cloned = nve.clone();
prop_assert_eq!(extract_str_value(&nve.expr), extract_str_value(&cloned.expr));
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(60))]
#[test]
fn escaped_regex_preserved(pat in simple_regex_pattern()) {
let src = format!(r##"pattern = r"{pat}""##);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, pat);
}
#[test]
fn escape_sequences_in_regular_strings(_i in 0..1u8) {
let src = r#"text = "\n\t""#;
let nve: NameValueExpr = syn::parse_str(src).unwrap();
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, "\n\t");
}
#[test]
fn unicode_escape_pattern(_i in 0..1u8) {
let src = r#"text = "\u{03B1}\u{03B2}""#;
let nve: NameValueExpr = syn::parse_str(src).unwrap();
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, "\u{03B1}\u{03B2}"); }
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(60))]
#[test]
fn string_literal_extraction(val in safe_pattern_string(20)) {
let src = format!(r#"text = "{val}""#);
let nve: NameValueExpr = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&nve.expr).unwrap();
prop_assert_eq!(extracted, val);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(60))]
#[test]
fn two_params_both_extractable(
ty in type_name(),
k1 in ident_strategy(),
k2 in ident_strategy(),
v1 in safe_pattern_string(6),
v2 in safe_pattern_string(6),
) {
prop_assume!(k1 != k2);
let src = format!(r#"{ty}, {k1} = "{v1}", {k2} = "{v2}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert_eq!(ftp.params.len(), 2);
let p1 = find_param(&ftp, &k1).unwrap();
let p2 = find_param(&ftp, &k2).unwrap();
prop_assert_eq!(extract_str_value(&p1.expr).unwrap(), v1);
prop_assert_eq!(extract_str_value(&p2.expr).unwrap(), v2);
}
#[test]
fn three_params_preserve_values(ty in type_name()) {
let src = format!(r##"{ty}, pattern = r"\d+", text = "hello", transform = "id""##);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert_eq!(ftp.params.len(), 3);
prop_assert_eq!(extract_str_value(&find_param(&ftp, "pattern").unwrap().expr).unwrap(), r"\d+");
prop_assert_eq!(extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap(), "hello");
prop_assert_eq!(extract_str_value(&find_param(&ftp, "transform").unwrap().expr).unwrap(), "id");
}
#[test]
fn param_ordering_preserved(
ty in type_name(),
k1 in ident_strategy(),
k2 in ident_strategy(),
) {
prop_assume!(k1 != k2);
let src = format!(r#"{ty}, {k1} = "a", {k2} = "b""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert_eq!(ftp.params[0].path.to_string(), k1);
prop_assert_eq!(ftp.params[1].path.to_string(), k2);
}
#[test]
fn missing_key_returns_none(ty in type_name(), val in safe_pattern_string(6)) {
let src = format!(r#"{ty}, text = "{val}""#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert!(find_param(&ftp, "nonexistent").is_none());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(40))]
#[test]
fn empty_text_pattern(ty in type_name()) {
let src = format!(r#"{ty}, text = """#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "text").unwrap().expr).unwrap();
prop_assert!(extracted.is_empty());
}
#[test]
fn empty_regex_pattern(ty in type_name()) {
let src = format!(r#"{ty}, pattern = """#);
let ftp: FieldThenParams = syn::parse_str(&src).unwrap();
let extracted = extract_str_value(&find_param(&ftp, "pattern").unwrap().expr).unwrap();
prop_assert!(extracted.is_empty());
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(60))]
#[test]
fn text_deterministic(val in safe_pattern_string(10)) {
let src = format!(r#"text = "{val}""#);
let a: NameValueExpr = syn::parse_str(&src).unwrap();
let b: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert_eq!(a, b);
}
#[test]
fn pattern_deterministic(pat in simple_regex_pattern()) {
let src = format!(r##"pattern = r"{pat}""##);
let a: NameValueExpr = syn::parse_str(&src).unwrap();
let b: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert_eq!(a, b);
}
#[test]
fn ftp_text_deterministic(ty in type_name(), val in safe_pattern_string(8)) {
let src = format!(r#"{ty}, text = "{val}""#);
let a: FieldThenParams = syn::parse_str(&src).unwrap();
let b: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert_eq!(a, b);
}
#[test]
fn ftp_pattern_deterministic(ty in type_name(), pat in simple_regex_pattern()) {
let src = format!(r##"{ty}, pattern = r"{pat}""##);
let a: FieldThenParams = syn::parse_str(&src).unwrap();
let b: FieldThenParams = syn::parse_str(&src).unwrap();
prop_assert_eq!(a, b);
}
#[test]
fn extracted_value_deterministic(val in safe_pattern_string(10)) {
let src = format!(r#"text = "{val}""#);
let a: NameValueExpr = syn::parse_str(&src).unwrap();
let b: NameValueExpr = syn::parse_str(&src).unwrap();
prop_assert_eq!(extract_str_value(&a.expr), extract_str_value(&b.expr));
}
#[test]
fn debug_deterministic(val in safe_pattern_string(10)) {
let src = format!(r#"text = "{val}""#);
let a: NameValueExpr = syn::parse_str(&src).unwrap();
let b: NameValueExpr = syn::parse_str(&src).unwrap();
let span_re = regex::Regex::new(r"bytes\(\d+\.\.\d+\)").unwrap();
let da = span_re.replace_all(&format!("{a:?}"), "bytes(..)").to_string();
let db = span_re.replace_all(&format!("{b:?}"), "bytes(..)").to_string();
prop_assert_eq!(da, db);
}
}