@@ -25,4 +25,134 @@ class Marcel::MimeType::MagicTest < Marcel::TestCase
2525 assert Marcel ::Magic . child? ( 'text/csv' , 'text/plain' )
2626 refute Marcel ::Magic . child? ( 'text/plain' , 'text/csv' )
2727 end
28+
29+ test "none of the regex patterns should match random test data" do
30+ ignore_list = %w( application/x-dbf )
31+
32+ extract_regexes = lambda do |matching_rules , collected = [ ] |
33+ matching_rules . each do |offset , value , children |
34+ collected << [ offset , value ] if value . is_a? ( Regexp )
35+ extract_regexes . call ( children , collected ) if children
36+ end
37+ collected
38+ end
39+
40+ # Use a test string that's very unlikely to match any file format regex
41+ # Using only high Unicode characters and very specific patterns
42+ test_data = "🇨🇭 \xFF \xFE \x03 \x05 \x06 🧀 cheese\x06 \x07 \x03 "
43+
44+ Marcel ::MAGIC . each do |type , matching_rules |
45+ next if ignore_list . include? ( type )
46+ regexes = extract_regexes . call ( matching_rules )
47+
48+ regexes . each do |offset , regex |
49+ buffer = ( +"" ) . encode ( Encoding ::BINARY )
50+
51+ result = Marcel ::Magic . send ( :match_regex , StringIO . new ( test_data ) , offset , regex , buffer )
52+
53+ assert_equal false , result , "Test data unexpectedly matched a file format regexp (#{ type } , #{ regex . inspect } )"
54+ end
55+ end
56+ end
57+
58+ test "nested match: parent AND child must both match" do
59+ # Rule: offset 0 matches "AAA" AND offset 3 matches "BBB"
60+ # This should match "AAABBB" but not "AAA" alone
61+ test_rules = [
62+ [ 0 , "AAA" . b , [ [ 3 , "BBB" . b ] ] ]
63+ ]
64+
65+ buffer = ( +"" ) . encode ( Encoding ::BINARY )
66+
67+ # Should match when both parent and child match
68+ io1 = StringIO . new ( "AAABBB" )
69+ assert Marcel ::Magic . send ( :magic_match_io , io1 , test_rules , buffer ) ,
70+ "Should match when parent and child both match"
71+
72+ # Should NOT match when parent matches but child doesn't
73+ io2 = StringIO . new ( "AAAXXX" )
74+ refute Marcel ::Magic . send ( :magic_match_io , io2 , test_rules , buffer ) ,
75+ "Should not match when parent matches but child doesn't"
76+ end
77+
78+ test "sibling matches use OR logic" do
79+ # Two sibling rules: either can match
80+ # Rule 1: offset 0 matches "XXX"
81+ # Rule 2: offset 0 matches "YYY"
82+ test_rules = [
83+ [ 0 , "XXX" . b ] ,
84+ [ 0 , "YYY" . b ]
85+ ]
86+
87+ buffer = ( +"" ) . encode ( Encoding ::BINARY )
88+
89+ # Should match via first sibling
90+ io1 = StringIO . new ( "XXX" )
91+ assert Marcel ::Magic . send ( :magic_match_io , io1 , test_rules , buffer ) ,
92+ "Should match via first sibling rule"
93+
94+ # Should match via second sibling
95+ io2 = StringIO . new ( "YYY" )
96+ assert Marcel ::Magic . send ( :magic_match_io , io2 , test_rules , buffer ) ,
97+ "Should match via second sibling rule"
98+
99+ # Should NOT match when no sibling matches
100+ io3 = StringIO . new ( "ZZZ" )
101+ refute Marcel ::Magic . send ( :magic_match_io , io3 , test_rules , buffer ) ,
102+ "Should not match when no sibling rule matches"
103+ end
104+
105+ test "parent with multiple child alternatives (OR)" do
106+ # Test complex nested structure: parent AND (child1 OR child2)
107+ # Parent at offset 0 matches "ROOT"
108+ # Child option 1: offset 4 matches "OPT1"
109+ # Child option 2: offset 4 matches "OPT2"
110+ test_rules = [
111+ [ 0 , "ROOT" . b , [
112+ [ 4 , "OPT1" . b ] , # First child option
113+ [ 4 , "OPT2" . b ] # Second child option (sibling OR)
114+ ] ]
115+ ]
116+
117+ buffer = ( +"" ) . encode ( Encoding ::BINARY )
118+
119+ # Should match when parent and first child match
120+ io1 = StringIO . new ( "ROOTOPT1" )
121+ assert Marcel ::Magic . send ( :magic_match_io , io1 , test_rules , buffer ) ,
122+ "Should match when parent and first child match"
123+
124+ # Should match when parent and second child match
125+ io2 = StringIO . new ( "ROOTOPT2" )
126+ assert Marcel ::Magic . send ( :magic_match_io , io2 , test_rules , buffer ) ,
127+ "Should match when parent and second child match"
128+
129+ # Should NOT match when parent matches but no child matches
130+ io3 = StringIO . new ( "ROOTXXXX" )
131+ refute Marcel ::Magic . send ( :magic_match_io , io3 , test_rules , buffer ) ,
132+ "Should not match when parent matches but no child matches"
133+ end
134+
135+ test "complex nested structure with multiple levels" do
136+ # Parent AND (Child AND Grandchild)
137+ # offset 0: "AAA", offset 3: "BBB", offset 6: "CCC"
138+ test_rules = [
139+ [ 0 , "AAA" . b , [
140+ [ 3 , "BBB" . b , [
141+ [ 6 , "CCC" . b ]
142+ ] ]
143+ ] ]
144+ ]
145+
146+ buffer = ( +"" ) . encode ( Encoding ::BINARY )
147+
148+ # Should match when all levels match
149+ io1 = StringIO . new ( "AAABBBCCC" )
150+ assert Marcel ::Magic . send ( :magic_match_io , io1 , test_rules , buffer ) ,
151+ "Should match when all nested levels match"
152+
153+ # Should NOT match when grandchild doesn't match
154+ io2 = StringIO . new ( "AAABBBXXX" )
155+ refute Marcel ::Magic . send ( :magic_match_io , io2 , test_rules , buffer ) ,
156+ "Should not match when deepest child doesn't match"
157+ end
28158end
0 commit comments