use rulefire::yara::ast::{
CompareOp, Condition, HexToken, OfQuantifier, StringModifiers, StringPattern, StringSelector,
StringSet,
};
use rulefire::yara::parser::parse_source;
#[test]
fn parse_minimal_valid_rule() {
let source = r#"
rule minimal {
condition:
true
}
"#;
let rules = parse_source(source, "minimal.yar").expect("should parse minimal rule");
assert_eq!(rules.len(), 1, "should have exactly one rule");
let rule = &rules[0];
assert_eq!(rule.name, "minimal");
assert!(rule.tags.is_empty(), "should have no tags");
assert!(rule.meta.is_empty(), "should have no meta");
assert!(rule.strings.is_empty(), "should have no strings");
assert_eq!(rule.condition, Condition::Bool(true));
}
#[test]
fn parse_rule_with_ten_strings_mixed_types() {
let source = r#"
rule ten_strings {
strings:
$text1 = "hello"
$text2 = "world"
$hex1 = { 48 65 6C 6C 6F }
$hex2 = { 90 ?? CC }
$regex1 = /test[0-9]+/
$regex2 = /(foo|bar)/
$text3 = "ascii" ascii
$text4 = "wide" wide
$text5 = "nocase" nocase
$text6 = "fullword" fullword
condition:
all of them
}
"#;
let rules = parse_source(source, "ten_strings.yar").expect("should parse rule with 10 strings");
assert_eq!(rules.len(), 1);
let rule = &rules[0];
assert_eq!(rule.strings.len(), 10, "should have 10 string declarations");
assert_eq!(rule.strings[0].identifier, "$text1");
assert_eq!(
rule.strings[0].pattern,
StringPattern::Text(b"hello".to_vec())
);
assert_eq!(rule.strings[1].identifier, "$text2");
assert_eq!(
rule.strings[1].pattern,
StringPattern::Text(b"world".to_vec())
);
assert_eq!(rule.strings[2].identifier, "$hex1");
match &rule.strings[2].pattern {
StringPattern::Hex(tokens) => {
assert_eq!(tokens.len(), 5);
assert_eq!(tokens[0], HexToken::Byte(0x48));
assert_eq!(tokens[4], HexToken::Byte(0x6F));
}
_ => panic!("expected hex pattern for $hex1"),
}
assert_eq!(rule.strings[3].identifier, "$hex2");
match &rule.strings[3].pattern {
StringPattern::Hex(tokens) => {
assert_eq!(tokens.len(), 3);
assert_eq!(tokens[0], HexToken::Byte(0x90));
assert_eq!(tokens[1], HexToken::Wildcard);
assert_eq!(tokens[2], HexToken::Byte(0xCC));
}
_ => panic!("expected hex pattern for $hex2"),
}
assert_eq!(rule.strings[4].identifier, "$regex1");
assert_eq!(
rule.strings[4].pattern,
StringPattern::Regex("test[0-9]+".to_string(), Default::default())
);
assert_eq!(rule.strings[5].identifier, "$regex2");
assert_eq!(
rule.strings[5].pattern,
StringPattern::Regex("(foo|bar)".to_string(), Default::default())
);
assert_eq!(
rule.strings[6].modifiers,
StringModifiers {
ascii: true,
..Default::default()
}
);
assert_eq!(
rule.strings[7].modifiers,
StringModifiers {
wide: true,
..Default::default()
}
);
assert_eq!(
rule.strings[8].modifiers,
StringModifiers {
nocase: true,
..Default::default()
}
);
assert_eq!(
rule.strings[9].modifiers,
StringModifiers {
fullword: true,
..Default::default()
}
);
}
#[test]
fn parse_all_of_them_condition() {
let source = r#"
rule all_of_them {
strings:
$a = "test"
condition:
all of them
}
"#;
let rules = parse_source(source, "all_of.yar").expect("should parse all of them");
assert_eq!(rules.len(), 1);
let expected = Condition::Of {
quantifier: OfQuantifier::All,
set: StringSet::Them,
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_filesize_comparison() {
let source = r#"
rule filesize_check {
condition:
filesize < 100KB
}
"#;
let rules = parse_source(source, "filesize.yar").expect("should parse filesize comparison");
assert_eq!(rules.len(), 1);
let expected = Condition::Compare {
left: Box::new(Condition::FileSize),
op: CompareOp::Lt,
right: Box::new(Condition::Integer(102400)), };
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_string_count_condition() {
let source = r#"
rule string_count {
strings:
$a = "test"
condition:
#a > 5
}
"#;
let rules = parse_source(source, "count.yar").expect("should parse string count");
assert_eq!(rules.len(), 1);
let expected = Condition::Compare {
left: Box::new(Condition::StringCount("$a".to_string())),
op: CompareOp::Gt,
right: Box::new(Condition::Integer(5)),
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_nested_boolean_logic() {
let source = r#"
rule nested_logic {
strings:
$a = "alpha"
$b = "beta"
$c = "gamma"
condition:
($a or $b) and not $c
}
"#;
let rules = parse_source(source, "nested.yar").expect("should parse nested logic");
assert_eq!(rules.len(), 1);
let expected = Condition::And(
Box::new(Condition::Or(
Box::new(Condition::StringMatch("$a".to_string())),
Box::new(Condition::StringMatch("$b".to_string())),
)),
Box::new(Condition::Not(Box::new(Condition::StringMatch(
"$c".to_string(),
)))),
);
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_nocase_modifier() {
let source = r#"
rule nocase_test {
strings:
$a = "test" nocase
$b = "test2" nocase wide
$c = "test3" ascii nocase fullword
condition:
any of them
}
"#;
let rules = parse_source(source, "nocase.yar").expect("should parse nocase modifier");
assert_eq!(rules.len(), 1);
let rule = &rules[0];
assert_eq!(rule.strings.len(), 3);
assert!(rule.strings[0].modifiers.nocase);
assert!(!rule.strings[0].modifiers.wide);
assert!(!rule.strings[0].modifiers.ascii);
assert!(!rule.strings[0].modifiers.fullword);
assert!(rule.strings[1].modifiers.nocase);
assert!(rule.strings[1].modifiers.wide);
assert!(rule.strings[2].modifiers.ascii);
assert!(rule.strings[2].modifiers.nocase);
assert!(rule.strings[2].modifiers.fullword);
}
#[test]
fn parse_malformed_missing_condition() {
let source = r#"
rule no_condition {
strings:
$a = "test"
}
"#;
let result = parse_source(source, "no_condition.yar");
assert!(result.is_err(), "should error on missing condition section");
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("missing condition") || err_msg.contains("condition"),
"error should mention missing condition: {}",
err_msg
);
}
#[test]
fn parse_malformed_unclosed_string() {
let source = r#"
rule unclosed_string {
strings:
$a = "test
condition:
$a
}
"#;
let result = parse_source(source, "unclosed.yar");
assert!(result.is_err(), "should error on unclosed string literal");
}
#[test]
fn parse_malformed_missing_rule_name() {
let source = r#"
rule {
condition:
true
}
"#;
let result = parse_source(source, "missing_name.yar");
assert!(result.is_err(), "should error on missing rule name");
}
#[test]
fn parse_empty_file() {
let source = "";
let rules = parse_source(source, "empty.yar").expect("should parse empty file");
assert!(
rules.is_empty(),
"empty file should produce empty rule list"
);
}
#[test]
fn parse_comments_only() {
let source = r#"
// This is a comment
/* Multi-line
comment */
// Another comment
"#;
let rules = parse_source(source, "comments.yar").expect("should parse comments-only file");
assert!(
rules.is_empty(),
"comments-only file should produce empty rule list"
);
}
#[test]
fn parse_whitespace_only() {
let source = " \n\t\n \r\n ";
let rules = parse_source(source, "whitespace.yar").expect("should parse whitespace-only file");
assert!(
rules.is_empty(),
"whitespace-only file should produce empty rule list"
);
}
#[test]
fn parse_real_signature_base_rule() {
let signature_base_path = std::path::Path::new("/tmp/signature-base");
if !signature_base_path.exists() {
let clone_result = std::process::Command::new("git")
.args([
"clone",
"--depth",
"1",
"https://github.com/Neo23x0/signature-base.git",
"/tmp/signature-base",
])
.output();
if clone_result.is_err() || !signature_base_path.exists() {
eprintln!("Skipping real rule test: signature-base not available and clone failed");
return;
}
}
let mut yar_files = Vec::new();
if let Ok(entries) = std::fs::read_dir(signature_base_path) {
for entry in entries.flatten() {
let path = entry.path();
if let Some(ext) = path.extension() {
if ext.to_string_lossy().to_lowercase() == "yar" {
yar_files.push(path);
}
}
}
}
if yar_files.is_empty() {
eprintln!("Skipping real rule test: no .yar files found in signature-base");
return;
}
let test_file = &yar_files[0];
eprintln!("Testing with real rule file: {}", test_file.display());
let result = parse_source(
&std::fs::read_to_string(test_file).expect("should read file"),
test_file,
);
match result {
Ok(rules) => {
eprintln!(
"Successfully parsed {} rules from {}",
rules.len(),
test_file.display()
);
for rule in &rules {
eprintln!(" - Rule: {} ({} strings)", rule.name, rule.strings.len());
}
}
Err(e) => {
eprintln!("Parser error (acceptable): {}", e);
}
}
}
#[test]
fn parse_any_of_them_condition() {
let source = r#"
rule any_of_them {
strings:
$a = "test"
condition:
any of them
}
"#;
let rules = parse_source(source, "any_of.yar").expect("should parse any of them");
let expected = Condition::Of {
quantifier: OfQuantifier::Any,
set: StringSet::Them,
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_n_of_them_condition() {
let source = r#"
rule two_of_them {
strings:
$a = "test"
$b = "test2"
condition:
2 of them
}
"#;
let rules = parse_source(source, "n_of.yar").expect("should parse N of them");
let expected = Condition::Of {
quantifier: OfQuantifier::N(2),
set: StringSet::Them,
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_rule_with_tags() {
let source = r#"
rule tagged_rule : malware trojan windows {
condition:
true
}
"#;
let rules = parse_source(source, "tags.yar").expect("should parse tagged rule");
let rule = &rules[0];
assert_eq!(rule.name, "tagged_rule");
assert_eq!(rule.tags, vec!["malware", "trojan", "windows"]);
}
#[test]
fn parse_rule_with_metadata() {
let source = r#"
rule meta_rule {
meta:
author = "Test Author"
description = "Test rule"
version = "1.0"
condition:
true
}
"#;
let rules = parse_source(source, "meta.yar").expect("should parse rule with metadata");
let rule = &rules[0];
assert_eq!(rule.meta.len(), 3);
assert_eq!(
rule.meta[0],
("author".to_string(), "Test Author".to_string())
);
assert_eq!(
rule.meta[1],
("description".to_string(), "Test rule".to_string())
);
assert_eq!(rule.meta[2], ("version".to_string(), "1.0".to_string()));
}
#[test]
fn parse_string_at_condition() {
let source = r#"
rule string_at {
strings:
$a = "MZ"
condition:
$a at 0
}
"#;
let rules = parse_source(source, "at.yar").expect("should parse string at");
match &rules[0].condition {
Condition::StringAt {
identifier,
position,
} => {
assert_eq!(identifier, "$a");
assert_eq!(**position, Condition::Integer(0));
}
_ => panic!("expected StringAt condition, got {:?}", rules[0].condition),
}
}
#[test]
fn parse_string_in_condition() {
let source = r#"
rule string_in {
strings:
$a = "test"
condition:
$a in (0..100)
}
"#;
let rules = parse_source(source, "in.yar").expect("should parse string in");
match &rules[0].condition {
Condition::StringIn {
identifier,
lower,
upper,
} => {
assert_eq!(identifier, "$a");
assert_eq!(**lower, Condition::Integer(0));
assert_eq!(**upper, Condition::Integer(100));
}
_ => panic!("expected StringIn condition, got {:?}", rules[0].condition),
}
}
#[test]
fn parse_string_offset_condition() {
let source = r#"
rule string_offset {
strings:
$a = "test"
condition:
@a == 0
}
"#;
let rules = parse_source(source, "offset.yar").expect("should parse string offset");
let expected = Condition::Compare {
left: Box::new(Condition::StringOffset {
identifier: "$a".to_string(),
index: 1,
}),
op: CompareOp::Eq,
right: Box::new(Condition::Integer(0)),
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_string_length_condition() {
let source = r#"
rule string_length {
strings:
$a = "test"
condition:
!a > 4
}
"#;
let rules = parse_source(source, "length.yar").expect("should parse string length");
let expected = Condition::Compare {
left: Box::new(Condition::StringLength {
identifier: "$a".to_string(),
index: 1,
}),
op: CompareOp::Gt,
right: Box::new(Condition::Integer(4)),
};
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_filesize_mb() {
let source = r#"
rule filesize_mb {
condition:
filesize < 5MB
}
"#;
let rules = parse_source(source, "filesize_mb.yar").expect("should parse filesize MB");
match &rules[0].condition {
Condition::Compare { left, op, right } => {
assert_eq!(**left, Condition::FileSize);
assert_eq!(*op, CompareOp::Lt);
assert_eq!(**right, Condition::Integer(5 * 1024 * 1024));
}
_ => panic!("expected Compare condition"),
}
}
#[test]
fn parse_multiple_rules() {
let source = r#"
rule first {
condition:
true
}
rule second {
strings:
$a = "test"
condition:
$a
}
rule third : tag1 tag2 {
meta:
key = "value"
condition:
false
}
"#;
let rules = parse_source(source, "multi.yar").expect("should parse multiple rules");
assert_eq!(rules.len(), 3);
assert_eq!(rules[0].name, "first");
assert_eq!(rules[1].name, "second");
assert_eq!(rules[2].name, "third");
assert!(rules[0].tags.is_empty());
assert!(rules[1].tags.is_empty());
assert_eq!(rules[2].tags, vec!["tag1", "tag2"]);
}
#[test]
fn parse_hex_string_all_wildcards() {
let source = r#"
rule hex_wildcards {
strings:
$a = { ?? ?? ?? }
condition:
$a
}
"#;
let rules = parse_source(source, "hex_wild.yar").expect("should parse hex wildcards");
match &rules[0].strings[0].pattern {
StringPattern::Hex(tokens) => {
assert_eq!(tokens.len(), 3);
assert!(tokens.iter().all(|t| *t == HexToken::Wildcard));
}
_ => panic!("expected hex pattern"),
}
}
#[test]
fn parse_all_modifiers() {
let source = r#"
rule all_mods {
strings:
$a = "test" ascii wide nocase fullword
condition:
$a
}
"#;
let rules = parse_source(source, "all_mods.yar").expect("should parse all modifiers");
let mods = &rules[0].strings[0].modifiers;
assert!(mods.ascii);
assert!(mods.wide);
assert!(mods.nocase);
assert!(mods.fullword);
}
#[test]
fn parse_invalid_hex_byte() {
let source = r#"
rule invalid_hex {
strings:
$a = { GG }
condition:
$a
}
"#;
let result = parse_source(source, "invalid_hex.yar");
assert!(result.is_err(), "should error on invalid hex byte 'GG'");
}
#[test]
fn parse_unclosed_hex_string() {
let source = r#"
rule unclosed_hex {
strings:
$a = { 48 65
condition:
$a
}
"#;
let result = parse_source(source, "unclosed_hex.yar");
assert!(result.is_err(), "should error on unclosed hex string");
}
#[test]
fn parse_escaped_string_literals() {
let source = r#"
rule escaped {
strings:
$a = "hello\nworld\ttest"
$b = "quote:\"test\""
$c = "backslash: \\"
condition:
any of them
}
"#;
let rules = parse_source(source, "escaped.yar").expect("should parse escaped strings");
assert_eq!(
rules[0].strings[0].pattern,
StringPattern::Text(b"hello\nworld\ttest".to_vec())
);
assert_eq!(
rules[0].strings[1].pattern,
StringPattern::Text(b"quote:\"test\"".to_vec())
);
assert_eq!(
rules[0].strings[2].pattern,
StringPattern::Text(b"backslash: \\".to_vec())
);
}
#[test]
fn parse_arithmetic_operations() {
let source = r#"
rule arithmetic {
condition:
1 + 2 == 3
}
"#;
let rules = parse_source(source, "arithmetic.yar").expect("should parse arithmetic");
match &rules[0].condition {
Condition::Compare { left, op, right } => {
assert_eq!(*op, CompareOp::Eq);
match &**left {
Condition::Add(l, r) => {
assert_eq!(**l, Condition::Integer(1));
assert_eq!(**r, Condition::Integer(2));
}
_ => panic!("expected Add"),
}
assert_eq!(**right, Condition::Integer(3));
}
_ => panic!("expected Compare"),
}
}
#[test]
fn parse_all_comparison_operators() {
let tests = vec![
("==", "eq", CompareOp::Eq),
("!=", "neq", CompareOp::Neq),
("<", "lt", CompareOp::Lt),
(">", "gt", CompareOp::Gt),
("<=", "lte", CompareOp::Lte),
(">=", "gte", CompareOp::Gte),
];
for (op_str, name_suffix, expected_op) in tests {
let source = format!(
r#"
rule compare_{} {{
strings:
$a = "test"
condition:
#a {} 5
}}
"#,
name_suffix, op_str
);
let rules = parse_source(&source, format!("compare_{}.yar", name_suffix))
.unwrap_or_else(|_| panic!("should parse {} operator", op_str));
match &rules[0].condition {
Condition::Compare { op, .. } => {
assert_eq!(
*op, expected_op,
"operator {} should parse correctly",
op_str
);
}
_ => panic!("expected Compare for operator {}", op_str),
}
}
}
#[test]
fn parse_deeply_nested_parentheses() {
let source = r#"
rule deep_nesting {
condition:
((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((true))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
}
"#;
let _ = parse_source(source, "deep.yar");
}
#[test]
fn parse_include_skipped_in_parse_source() {
let source = r#"
include "other.yar"
rule with_include {
condition:
true
}
"#;
let result = parse_source(source, "with_include.yar");
assert!(
result.is_ok(),
"include in parse_source should be silently skipped"
);
assert_eq!(result.unwrap().len(), 1);
}
#[test]
fn parse_long_identifier() {
let long_name = "a".repeat(1000);
let source = format!(
r#"
rule {} {{
condition:
true
}}
"#,
long_name
);
let rules = parse_source(&source, "long_id.yar").expect("should parse long identifier");
assert_eq!(rules[0].name, long_name);
}
#[test]
fn parse_unicode_in_comments() {
let source = r#"
// This is a comment with unicode: 你好世界 🎉
rule unicode_comment {
/* Another unicode comment: café résumé */
condition:
true
}
"#;
let rules = parse_source(source, "unicode.yar").expect("should parse with unicode comments");
assert_eq!(rules[0].name, "unicode_comment");
}
#[test]
fn parse_case_sensitivity() {
let source_lower = r#"
rule lowercase {
condition:
true
}
"#;
assert!(parse_source(source_lower, "lower.yar").is_ok());
let source_upper = r#"
RULE uppercase {
CONDITION:
TRUE
}
"#;
assert!(parse_source(source_upper, "upper.yar").is_err());
}
#[test]
fn parse_for_loop() {
let source = r#"
rule for_loop_test {
strings:
$a = "test"
condition:
for any i in (0..10) : ( $a at i )
}
"#;
let rules = parse_source(source, "for_loop.yar").expect("should parse for loop");
match &rules[0].condition {
Condition::For {
quantifier,
variable,
range,
body,
} => {
assert!(matches!(
quantifier,
rulefire::yara::ast::LoopQuantifier::Any
));
assert_eq!(variable, "i");
assert_eq!(*range.lower, Condition::Integer(0));
assert_eq!(*range.upper, Condition::Integer(10));
match &**body {
Condition::StringAt {
identifier,
position,
} => {
assert_eq!(identifier, "$a");
assert!(matches!(**position, Condition::Identifier(ref s) if s == "i"));
}
_ => panic!("expected StringAt in loop body, got {:?}", body),
}
}
_ => panic!("expected For condition, got {:?}", rules[0].condition),
}
}
#[test]
fn parse_of_with_explicit_list() {
let source = r#"
rule of_list {
strings:
$a = "alpha"
$b = "beta"
$c = "gamma"
condition:
2 of ($a, $b)
}
"#;
let rules = parse_source(source, "of_list.yar").expect("should parse of with list");
match &rules[0].condition {
Condition::Of { quantifier, set } => {
assert_eq!(*quantifier, OfQuantifier::N(2));
match set {
StringSet::Identifiers(selectors) => {
assert_eq!(selectors.len(), 2);
assert_eq!(selectors[0], StringSelector::Identifier("$a".to_string()));
assert_eq!(selectors[1], StringSelector::Identifier("$b".to_string()));
}
_ => panic!("expected Identifiers set"),
}
}
_ => panic!("expected Of condition"),
}
}
#[test]
fn parse_of_with_prefix_selector() {
let source = r#"
rule prefix_select {
strings:
$a1 = "test1"
$a2 = "test2"
$b = "other"
condition:
all of ($a*)
}
"#;
let rules = parse_source(source, "prefix.yar").expect("should parse of with prefix");
match &rules[0].condition {
Condition::Of { quantifier, set } => {
assert!(matches!(quantifier, OfQuantifier::All));
match set {
StringSet::Identifiers(selectors) => {
assert_eq!(selectors.len(), 1);
assert_eq!(selectors[0], StringSelector::Prefix("$a".to_string()));
}
_ => panic!("expected Identifiers set"),
}
}
_ => panic!("expected Of condition"),
}
}
#[test]
fn parse_regex_with_escapes() {
let source = r#"
rule regex_escapes {
strings:
$a = /test\/[0-9]+\/end/
condition:
$a
}
"#;
let rules = parse_source(source, "regex_esc.yar").expect("should parse regex with escapes");
assert_eq!(
rules[0].strings[0].pattern,
StringPattern::Regex(r"test\/[0-9]+\/end".to_string(), Default::default())
);
}
#[test]
fn parse_multiline_string_behavior() {
let source = r#"
rule multiline {
strings:
$a = "line1
line2"
condition:
$a
}
"#;
let result = parse_source(source, "multiline.yar");
match result {
Ok(_) => { }
Err(_) => { }
}
}
#[test]
fn parse_empty_rule_body() {
let source = r#"
rule empty_body {
}
"#;
let result = parse_source(source, "empty_body.yar");
assert!(result.is_err(), "empty rule body should cause error");
}
#[test]
fn parse_boolean_literals() {
let source = r#"
rule bool_literals {
condition:
true and not false
}
"#;
let rules = parse_source(source, "bool.yar").expect("should parse boolean literals");
let expected = Condition::And(
Box::new(Condition::Bool(true)),
Box::new(Condition::Not(Box::new(Condition::Bool(false)))),
);
assert_eq!(rules[0].condition, expected);
}
#[test]
fn parse_identifier_condition() {
let source = r#"
rule identifier_cond {
condition:
pe_number_of_sections > 2
}
"#;
let rules = parse_source(source, "ident.yar").expect("should parse identifier condition");
if let Condition::Compare { op, right, .. } = &rules[0].condition {
assert_eq!(*op, CompareOp::Gt);
assert_eq!(**right, Condition::Integer(2));
}
}
#[test]
fn parse_concurrent_stress() {
use std::sync::Arc;
use std::thread;
let source = Arc::new(
r#"
rule concurrent_test {
strings:
$a = "test"
$b = { 48 65 6C }
$c = /regex[0-9]+/
condition:
all of them and filesize < 100KB and #a > 0
}
"#
.to_string(),
);
let mut handles = vec![];
for i in 0..10 {
let src = Arc::clone(&source);
handles.push(thread::spawn(move || {
let rules = parse_source(&src, format!("concurrent_{}.yar", i)).expect("should parse");
assert_eq!(rules.len(), 1);
assert_eq!(rules[0].name, "concurrent_test");
assert_eq!(rules[0].strings.len(), 3);
}));
}
for handle in handles {
handle.join().expect("thread should complete");
}
}
#[test]
fn parse_large_rule_count() {
let mut source = String::new();
for i in 0..100 {
source.push_str(&format!(
r#"
rule rule_{} {{
strings:
$a = "test{}"
condition:
$a
}}
"#,
i, i
));
}
let rules = parse_source(&source, "large.yar").expect("should parse many rules");
assert_eq!(rules.len(), 100);
for (i, rule) in rules.iter().enumerate() {
assert_eq!(rule.name, format!("rule_{}", i));
}
}