2
2
3
3
import unittest
4
4
import os
5
+ import string
5
6
import warnings
6
7
7
8
from fnmatch import fnmatch , fnmatchcase , translate , filter
@@ -45,6 +46,13 @@ def test_fnmatch(self):
45
46
check ('\n foo' , 'foo*' , False )
46
47
check ('\n ' , '*' )
47
48
49
+ def test_slow_fnmatch (self ):
50
+ check = self .check_match
51
+ check ('a' * 50 , '*a*a*a*a*a*a*a*a*a*a' )
52
+ # The next "takes forever" if the regexp translation is
53
+ # straightforward. See bpo-40480.
54
+ check ('a' * 50 + 'b' , '*a*a*a*a*a*a*a*a*a*a' , False )
55
+
48
56
def test_mix_bytes_str (self ):
49
57
self .assertRaises (TypeError , fnmatch , 'test' , b'*' )
50
58
self .assertRaises (TypeError , fnmatch , b'test' , '*' )
@@ -89,6 +97,119 @@ def test_sep(self):
89
97
check ('usr/bin' , 'usr\\ bin' , normsep )
90
98
check ('usr\\ bin' , 'usr\\ bin' )
91
99
100
+ def test_char_set (self ):
101
+ ignorecase = os .path .normcase ('ABC' ) == os .path .normcase ('abc' )
102
+ check = self .check_match
103
+ tescases = string .ascii_lowercase + string .digits + string .punctuation
104
+ for c in tescases :
105
+ check (c , '[az]' , c in 'az' )
106
+ check (c , '[!az]' , c not in 'az' )
107
+ # Case insensitive.
108
+ for c in tescases :
109
+ check (c , '[AZ]' , (c in 'az' ) and ignorecase )
110
+ check (c , '[!AZ]' , (c not in 'az' ) or not ignorecase )
111
+ for c in string .ascii_uppercase :
112
+ check (c , '[az]' , (c in 'AZ' ) and ignorecase )
113
+ check (c , '[!az]' , (c not in 'AZ' ) or not ignorecase )
114
+ # Repeated same character.
115
+ for c in tescases :
116
+ check (c , '[aa]' , c == 'a' )
117
+ # Special cases.
118
+ for c in tescases :
119
+ check (c , '[^az]' , c in '^az' )
120
+ check (c , '[[az]' , c in '[az' )
121
+ check (c , r'[!]]' , c != ']' )
122
+ check ('[' , '[' )
123
+ check ('[]' , '[]' )
124
+ check ('[!' , '[!' )
125
+ check ('[!]' , '[!]' )
126
+
127
+ def test_range (self ):
128
+ ignorecase = os .path .normcase ('ABC' ) == os .path .normcase ('abc' )
129
+ normsep = os .path .normcase ('\\ ' ) == os .path .normcase ('/' )
130
+ check = self .check_match
131
+ tescases = string .ascii_lowercase + string .digits + string .punctuation
132
+ for c in tescases :
133
+ check (c , '[b-d]' , c in 'bcd' )
134
+ check (c , '[!b-d]' , c not in 'bcd' )
135
+ check (c , '[b-dx-z]' , c in 'bcdxyz' )
136
+ check (c , '[!b-dx-z]' , c not in 'bcdxyz' )
137
+ # Case insensitive.
138
+ for c in tescases :
139
+ check (c , '[B-D]' , (c in 'bcd' ) and ignorecase )
140
+ check (c , '[!B-D]' , (c not in 'bcd' ) or not ignorecase )
141
+ for c in string .ascii_uppercase :
142
+ check (c , '[b-d]' , (c in 'BCD' ) and ignorecase )
143
+ check (c , '[!b-d]' , (c not in 'BCD' ) or not ignorecase )
144
+ # Upper bound == lower bound.
145
+ for c in tescases :
146
+ check (c , '[b-b]' , c == 'b' )
147
+ # Special cases.
148
+ for c in tescases :
149
+ check (c , '[!-#]' , c not in '-#' )
150
+ check (c , '[!--.]' , c not in '-.' )
151
+ check (c , '[^-`]' , c in '^_`' )
152
+ if not (normsep and c == '/' ):
153
+ check (c , '[[-^]' , c in r'[\]^' )
154
+ check (c , r'[\-^]' , c in r'\]^' )
155
+ check (c , '[b-]' , c in '-b' )
156
+ check (c , '[!b-]' , c not in '-b' )
157
+ check (c , '[-b]' , c in '-b' )
158
+ check (c , '[!-b]' , c not in '-b' )
159
+ check (c , '[-]' , c in '-' )
160
+ check (c , '[!-]' , c not in '-' )
161
+ # Upper bound is less that lower bound: error in RE.
162
+ for c in tescases :
163
+ check (c , '[d-b]' , False )
164
+ check (c , '[!d-b]' , True )
165
+ check (c , '[d-bx-z]' , c in 'xyz' )
166
+ check (c , '[!d-bx-z]' , c not in 'xyz' )
167
+ check (c , '[d-b^-`]' , c in '^_`' )
168
+ if not (normsep and c == '/' ):
169
+ check (c , '[d-b[-^]' , c in r'[\]^' )
170
+
171
+ def test_sep_in_char_set (self ):
172
+ normsep = os .path .normcase ('\\ ' ) == os .path .normcase ('/' )
173
+ check = self .check_match
174
+ check ('/' , r'[/]' )
175
+ check ('\\ ' , r'[\]' )
176
+ check ('/' , r'[\]' , normsep )
177
+ check ('\\ ' , r'[/]' , normsep )
178
+ check ('[/]' , r'[/]' , False )
179
+ check (r'[\\]' , r'[/]' , False )
180
+ check ('\\ ' , r'[\t]' )
181
+ check ('/' , r'[\t]' , normsep )
182
+ check ('t' , r'[\t]' )
183
+ check ('\t ' , r'[\t]' , False )
184
+
185
+ def test_sep_in_range (self ):
186
+ normsep = os .path .normcase ('\\ ' ) == os .path .normcase ('/' )
187
+ check = self .check_match
188
+ check ('a/b' , 'a[.-0]b' , not normsep )
189
+ check ('a\\ b' , 'a[.-0]b' , False )
190
+ check ('a\\ b' , 'a[Z-^]b' , not normsep )
191
+ check ('a/b' , 'a[Z-^]b' , False )
192
+
193
+ check ('a/b' , 'a[/-0]b' , not normsep )
194
+ check (r'a\b' , 'a[/-0]b' , False )
195
+ check ('a[/-0]b' , 'a[/-0]b' , False )
196
+ check (r'a[\-0]b' , 'a[/-0]b' , False )
197
+
198
+ check ('a/b' , 'a[.-/]b' )
199
+ check (r'a\b' , 'a[.-/]b' , normsep )
200
+ check ('a[.-/]b' , 'a[.-/]b' , False )
201
+ check (r'a[.-\]b' , 'a[.-/]b' , False )
202
+
203
+ check (r'a\b' , r'a[\-^]b' )
204
+ check ('a/b' , r'a[\-^]b' , normsep )
205
+ check (r'a[\-^]b' , r'a[\-^]b' , False )
206
+ check ('a[/-^]b' , r'a[\-^]b' , False )
207
+
208
+ check (r'a\b' , r'a[Z-\]b' , not normsep )
209
+ check ('a/b' , r'a[Z-\]b' , False )
210
+ check (r'a[Z-\]b' , r'a[Z-\]b' , False )
211
+ check ('a[Z-/]b' , r'a[Z-\]b' , False )
212
+
92
213
def test_warnings (self ):
93
214
with warnings .catch_warnings ():
94
215
warnings .simplefilter ('error' , Warning )
@@ -104,6 +225,7 @@ def test_warnings(self):
104
225
class TranslateTestCase (unittest .TestCase ):
105
226
106
227
def test_translate (self ):
228
+ import re
107
229
self .assertEqual (translate ('*' ), r'(?s:.*)\Z' )
108
230
self .assertEqual (translate ('?' ), r'(?s:.)\Z' )
109
231
self .assertEqual (translate ('a?b*' ), r'(?s:a.b.*)\Z' )
@@ -112,7 +234,34 @@ def test_translate(self):
112
234
self .assertEqual (translate ('[!x]' ), r'(?s:[^x])\Z' )
113
235
self .assertEqual (translate ('[^x]' ), r'(?s:[\^x])\Z' )
114
236
self .assertEqual (translate ('[x' ), r'(?s:\[x)\Z' )
115
-
237
+ # from the docs
238
+ self .assertEqual (translate ('*.txt' ), r'(?s:.*\.txt)\Z' )
239
+ # squash consecutive stars
240
+ self .assertEqual (translate ('*********' ), r'(?s:.*)\Z' )
241
+ self .assertEqual (translate ('A*********' ), r'(?s:A.*)\Z' )
242
+ self .assertEqual (translate ('*********A' ), r'(?s:.*A)\Z' )
243
+ self .assertEqual (translate ('A*********?[?]?' ), r'(?s:A.*.[?].)\Z' )
244
+ # fancy translation to prevent exponential-time match failure
245
+ t = translate ('**a*a****a' )
246
+ digits = re .findall (r'\d+' , t )
247
+ self .assertEqual (len (digits ), 4 )
248
+ self .assertEqual (digits [0 ], digits [1 ])
249
+ self .assertEqual (digits [2 ], digits [3 ])
250
+ g1 = f"g{ digits [0 ]} " # e.g., group name "g4"
251
+ g2 = f"g{ digits [2 ]} " # e.g., group name "g5"
252
+ self .assertEqual (t ,
253
+ fr'(?s:(?=(?P<{ g1 } >.*?a))(?P={ g1 } )(?=(?P<{ g2 } >.*?a))(?P={ g2 } ).*a)\Z' )
254
+ # and try pasting multiple translate results - it's an undocumented
255
+ # feature that this works; all the pain of generating unique group
256
+ # names across calls exists to support this
257
+ r1 = translate ('**a**a**a*' )
258
+ r2 = translate ('**b**b**b*' )
259
+ r3 = translate ('*c*c*c*' )
260
+ fatre = "|" .join ([r1 , r2 , r3 ])
261
+ self .assertTrue (re .match (fatre , 'abaccad' ))
262
+ self .assertTrue (re .match (fatre , 'abxbcab' ))
263
+ self .assertTrue (re .match (fatre , 'cbabcaxc' ))
264
+ self .assertFalse (re .match (fatre , 'dabccbad' ))
116
265
117
266
class FilterTestCase (unittest .TestCase ):
118
267
0 commit comments