use super::parse_regex_getstr_value;
use crate::parser::ast::{TypeKind, Value};
use crate::parser::grammar::parse_magic_rule;
#[test]
fn getstr_resolver_content_fidelity_table() {
let cases: &[(&str, &str)] = &[
(r"\^[\040\t]{0,50}\\.asciiz", "^[ \t]{0,50}\\.asciiz"),
(r"[\040]", "[ ]"),
(r"\t", "\t"),
(r"\^", "^"),
(r"\\.", "\\."),
(r"\\\\", "\\\\"),
(r"\x41", "A"),
(r"\377", "\\xff"),
(r"\xFF", "\\xff"),
(r"\b", "\x08"),
(r"\a", "\x07"),
(r"\f", "\x0C"),
(r"\n", "\n"),
(r"\r", "\r"),
(r"\v", "\x0B"),
];
for &(input, expected) in cases {
let (remaining, value) =
parse_regex_getstr_value(input).unwrap_or_else(|e| panic!("{input:?}: {e:?}"));
assert_eq!(remaining, "", "remaining input for {input:?}");
assert_eq!(
value,
Value::String(expected.to_string()),
"resolved text for input {input:?}"
);
}
}
#[test]
fn getstr_resolver_ge_0x80_bytes_are_valid_utf8_never_raw() {
for input in [r"\377", r"\xFF", r"\x80", r"\200"] {
let (_, value) = parse_regex_getstr_value(input).expect(input);
let Value::String(s) = value else {
panic!("expected Value::String for {input:?}");
};
assert!(
s.bytes().all(|b| b < 0x80),
"resolved text for {input:?} must be pure ASCII (the >= 0x80 byte is \
re-encoded as \\xHH text, never appended raw): got {s:?}"
);
assert!(
std::str::from_utf8(s.as_bytes()).is_ok(),
"resolved text for {input:?} must be valid UTF-8"
);
}
}
#[test]
fn getstr_resolver_hex_escape_with_no_digits_falls_back_to_literal_x() {
let (remaining, value) = parse_regex_getstr_value(r"\xZZ").expect(r"\xZZ");
assert_eq!(value, Value::String("xZZ".to_string()));
assert_eq!(remaining, "");
}
#[test]
fn getstr_resolver_stops_at_unescaped_whitespace() {
let (remaining, value) = parse_regex_getstr_value("foobar[0-9]+ trailing message").unwrap();
assert_eq!(value, Value::String("foobar[0-9]+".to_string()));
assert_eq!(remaining, " trailing message");
}
#[test]
fn getstr_resolver_rejects_empty_or_whitespace_only_input() {
assert!(parse_regex_getstr_value("").is_err());
assert!(parse_regex_getstr_value(" ").is_err());
}
#[test]
fn getstr_resolver_rejects_lone_trailing_backslash_incomplete_escape() {
let result = parse_regex_getstr_value("\\");
assert!(
result.is_err(),
"a lone trailing backslash with nothing following it must be rejected \
(incomplete escape resolves to zero characters), got {result:?}"
);
}
#[test]
fn parse_magic_rule_regex_lone_trailing_backslash_pattern_recovers_via_value_fallback() {
let input = "0 regex \\";
let (remaining, rule) = parse_magic_rule(input)
.expect("the parse_value fallback must recover a bare trailing backslash gracefully");
assert_eq!(remaining, "");
assert!(matches!(rule.typ, TypeKind::Regex { .. }));
assert_eq!(
rule.value,
Value::Bytes(vec![0x5c]),
"parse_value's hex/mixed-ascii fallback captures the lone backslash as a raw byte"
);
}
#[test]
fn parse_magic_rule_regex_empty_pattern_errs_gracefully() {
let input = "0 regex ";
let result = parse_magic_rule(input);
assert!(
result.is_err(),
"a regex rule with a completely empty value token must be rejected \
gracefully (Err), never panic: got {result:?}"
);
}
#[test]
fn parse_magic_rule_regex_escaped_pattern_yields_string_not_bytes() {
let input = r"0 regex \^[\040\t]{0,50}\\.asciiz assembler source text";
let (remaining, rule) = parse_magic_rule(input).expect(input);
assert_eq!(remaining, "");
assert!(matches!(rule.typ, TypeKind::Regex { .. }));
assert_eq!(
rule.value,
Value::String("^[ \t]{0,50}\\.asciiz".to_string()),
"regex pattern must resolve to Value::String, not Value::Bytes"
);
assert_eq!(rule.message, "assembler source text");
}
#[test]
fn parse_magic_rule_regex_non_escaped_pattern_still_value_string() {
let input = "0 regex foobar[0-9]+ numeric suffix";
let (remaining, rule) = parse_magic_rule(input).expect(input);
assert_eq!(remaining, "");
assert_eq!(rule.value, Value::String("foobar[0-9]+".to_string()));
assert_eq!(rule.message, "numeric suffix");
}
#[test]
fn parse_magic_rule_regex_quoted_pattern_unaffected_by_getstr_routing() {
let input = r#"0 regex/c "hello" case-insensitive match"#;
let (remaining, rule) = parse_magic_rule(input).expect(input);
assert_eq!(remaining, "");
assert_eq!(rule.value, Value::String("hello".to_string()));
}
#[test]
fn parse_magic_rule_search_with_same_escaped_pattern_drops_unknown_escape_backslash() {
let input = r"0 search/80 \^[\040\t]{0,50}\\.asciiz test";
let (remaining, rule) = parse_magic_rule(input).expect(input);
assert_eq!(remaining, "");
assert!(matches!(rule.typ, TypeKind::Search { .. }));
match &rule.value {
Value::Bytes(bytes) => {
assert_eq!(
bytes, b"^[ \t]{0,50}\\.asciiz",
"unrecognized `\\^` escape drops the backslash (getstr parity), \
matching the regex path's resolved bytes"
);
}
other => panic!("expected search to still produce Value::Bytes, got {other:?}"),
}
assert_eq!(rule.message, "test");
}
#[test]
fn assembler_asciiz_rule_matches_gnu_file_semantics_end_to_end() {
let line = "0\tregex\t\\^[\\040\\t]{0,50}\\\\.asciiz\t\tassembler source text";
let (_, rule) = parse_magic_rule(line).expect(line);
let Value::String(pattern) = &rule.value else {
panic!("expected Value::String, got {:?}", rule.value);
};
let re = regex::bytes::Regex::new(pattern).expect("resolved pattern must compile");
assert!(
re.is_match(b"\t.asciiz \"hi\""),
"leading tab then .asciiz must match"
);
assert!(
re.is_match(b".asciiz \"hi\""),
"zero leading whitespace (lower bound of {{0,50}}) must match"
);
assert!(
!re.is_match(b"xyz .asciiz"),
"non-whitespace before .asciiz must not match"
);
assert!(
!re.is_match(b"Xasciiz"),
"escaped dot must be a literal '.', not the any-character wildcard"
);
let too_much_padding = format!("{}{}", " ".repeat(51), ".asciiz");
assert!(
!re.is_match(too_much_padding.as_bytes()),
"more than 50 leading whitespace chars must exceed the {{0,50}} upper bound"
);
}
#[test]
fn parse_magic_rule_regex_with_ge_0x80_escape_does_not_abort_parse() {
let input = r"0 regex \377 high byte octal escape";
let (remaining, rule) = parse_magic_rule(input).expect(input);
assert_eq!(remaining, "");
assert_eq!(rule.value, Value::String("\\xff".to_string()));
let input_hex = r"0 regex \xFF high byte hex escape";
let (remaining, rule) = parse_magic_rule(input_hex).expect(input_hex);
assert_eq!(remaining, "");
assert_eq!(rule.value, Value::String("\\xff".to_string()));
}