6
6
import time
7
7
import warnings
8
8
from contextlib import closing
9
- from itertools import repeat
10
9
from typing import (
11
10
AnyStr ,
12
11
Dict ,
13
- Iterator ,
14
12
List ,
15
13
Match ,
16
14
Optional ,
41
39
Regex : TypeAlias = Union [str , bytes , Pattern ]
42
40
43
41
42
+ def _format_logline (s ):
43
+ if not s :
44
+ return "<EOF>\n "
45
+ if len (s ) <= 100 :
46
+ return s
47
+ return f"{ s [:100 ]} ... ({ len (s ) - 100 } chars omitted)"
48
+
49
+
44
50
def match_regexps_in_file (
45
51
logpath : os .PathLike , log_extracts : List [Pattern ]
46
52
) -> Tuple [bool , Dict [str , str ], List [Pattern ]]:
@@ -109,11 +115,11 @@ def __enter__(self):
109
115
def __exit__ (self , exc_type , exc_value , traceback ):
110
116
if exc_type is not None :
111
117
return False
112
- s_pos = self .log_matcher .position
113
118
m = self .log_matcher .match (
114
119
self .regex , self .timeout , raise_on_timeout = False
115
120
)
116
- e_pos = self .log_matcher .position
121
+ s_pos = self .log_matcher ._debug_info_s [0 ]
122
+ e_pos = self .log_matcher ._debug_info_e [0 ]
117
123
if m is not None :
118
124
self .match_results .append ((m , self .regex , s_pos , e_pos ))
119
125
else :
@@ -142,6 +148,9 @@ def __init__(
142
148
self .position : Optional [LogPosition ] = None
143
149
self .log_stream : FileLogStream = self ._create_log_stream ()
144
150
151
+ self ._debug_info_s = ()
152
+ self ._debug_info_e = ()
153
+
145
154
# deprecation helpers
146
155
self .had_transformed = False
147
156
@@ -241,41 +250,44 @@ def mark(self, name: str):
241
250
"""
242
251
self .marks [name ] = self .position
243
252
244
- def match (
253
+ def _match (
245
254
self ,
246
- regex : Regex ,
247
- timeout : float = LOG_MATCHER_DEFAULT_TIMEOUT ,
248
- raise_on_timeout : bool = True ,
255
+ regex : Pattern [AnyStr ],
256
+ timeout : float ,
249
257
) -> Optional [Match ]:
250
258
"""
251
- Matches each line in the log file from the current line number to the
252
- end of the file. If a match is found the line number is stored and the
253
- match is returned. By default an exception is raised if no match is
254
- found.
259
+ Base block for ``match``, ``not_match`` & ``match_all``,
260
+ as well as certain ``LogfileNamespace`` assertions.
255
261
256
- :param regex: Regex string or compiled regular expression
257
- (``re.compile``)
262
+ :param regex: Checked regular expression
258
263
:param timeout: Timeout in seconds to wait for matching process,
259
264
0 means matching till EOF and not waiting for new lines, any
260
265
value greater than 0 means doing matching up to such seconds,
261
266
defaults to 5 seconds
262
- :param raise_on_timeout: To raise TimeoutException or not
263
267
:return: The regex match or None if no match is found
264
268
"""
265
269
match = None
266
270
start_time = time .time ()
267
271
end_time = start_time + timeout
268
-
269
272
regex = self ._prepare_regexp (regex )
270
273
271
274
with closing (self .log_stream ) as log :
272
275
log .seek (self .position )
273
276
277
+ non_eof = ""
274
278
while True :
275
- if timeout > 0 and time .time () > end_time :
276
- break
277
279
line = log .readline ()
280
+ if self ._debug_info_s is None :
281
+ self ._debug_info_s = (
282
+ str (self .position )
283
+ if self .position is not None
284
+ else "<BOF>" ,
285
+ start_time ,
286
+ _format_logline (line ),
287
+ )
288
+
278
289
if line :
290
+ non_eof = line
279
291
match = regex .match (line )
280
292
if match :
281
293
break
@@ -284,20 +296,62 @@ def match(
284
296
else :
285
297
break
286
298
299
+ if timeout > 0 and time .time () > end_time :
300
+ break
301
+
287
302
self .position = self .log_stream .position
303
+ if self ._debug_info_e is None :
304
+ self ._debug_info_e = (
305
+ str (self .position ),
306
+ time .time (),
307
+ _format_logline (non_eof ),
308
+ )
288
309
289
- if match is not None :
310
+ return match
311
+
312
+ def match (
313
+ self ,
314
+ regex : Regex ,
315
+ timeout : float = LOG_MATCHER_DEFAULT_TIMEOUT ,
316
+ raise_on_timeout : bool = True ,
317
+ ) -> Optional [Match ]:
318
+ """
319
+ Matches each line in the log file from the current line number to the
320
+ end of the file. If a match is found the line number is stored and the
321
+ match is returned. By default an exception is raised if no match is
322
+ found.
323
+
324
+ :param regex: Regex string or compiled regular expression
325
+ (``re.compile``)
326
+ :param timeout: Timeout in seconds to wait for matching process,
327
+ 0 means matching till EOF and not waiting for new lines, any
328
+ value greater than 0 means doing matching up to such seconds,
329
+ defaults to 5 seconds
330
+ :param raise_on_timeout: To raise TimeoutException or not
331
+ :return: The regex match or None if no match is found
332
+ """
333
+ self ._debug_info_s = None
334
+ self ._debug_info_e = None
335
+ regex = self ._prepare_regexp (regex )
336
+
337
+ m = self ._match (regex , timeout = timeout )
338
+
339
+ if m is None :
290
340
self .logger .debug (
291
- "Match[%s] found in %.2fs" ,
341
+ "%s: no expected match[%s] found,\n search starting from %s (around %s), "
342
+ "where first line seen as:\n %s"
343
+ "and ending at %s (around %s), where last line seen as:\n %s" ,
344
+ self ,
292
345
regex .pattern ,
293
- time .time () - start_time ,
294
- )
295
- elif timeout and raise_on_timeout :
296
- raise timing .TimeoutException (
297
- "No match[{}] found in {}s" .format (regex .pattern , timeout )
346
+ * self ._debug_info_s ,
347
+ * self ._debug_info_e ,
298
348
)
349
+ if timeout and raise_on_timeout :
350
+ raise timing .TimeoutException (
351
+ "No match[%s] found in %.2fs." , regex .pattern , timeout
352
+ )
299
353
300
- return match
354
+ return m
301
355
302
356
def not_match (
303
357
self ,
@@ -316,9 +370,22 @@ def not_match(
316
370
0 means should not wait and return whatever matched on initial
317
371
scan, defaults to 5 seconds
318
372
"""
373
+ self ._debug_info_s = None
374
+ self ._debug_info_e = None
375
+ regex = self ._prepare_regexp (regex )
376
+
377
+ m = self ._match (regex , timeout )
319
378
320
- match = self .match (regex , timeout , raise_on_timeout = False )
321
- if match is not None :
379
+ if m is not None :
380
+ self .logger .debug (
381
+ "%s: unexpected match[%s] found,\n search starting from %s (around %s), "
382
+ "where first line seen as:\n %s"
383
+ "and ending at %s (around %s), where last line seen as:\n %s" ,
384
+ self ,
385
+ regex .pattern ,
386
+ * self ._debug_info_s ,
387
+ * self ._debug_info_e ,
388
+ )
322
389
raise Exception (
323
390
f"Unexpected match[{ regex .pattern } ] found in { timeout } s"
324
391
)
@@ -343,15 +410,37 @@ def match_all(
343
410
matches = []
344
411
end_time = time .time () + timeout
345
412
346
- try :
347
- while timeout >= 0 :
348
- matches .append (
349
- self .match (regex , timeout , raise_on_timeout = True )
413
+ self ._debug_info_s = None
414
+ regex = self ._prepare_regexp (regex )
415
+
416
+ while True :
417
+ if timeout == 0 :
418
+ t = 0
419
+ else :
420
+ t = end_time - time .time ()
421
+ if t <= 0 :
422
+ break
423
+ self ._debug_info_e = None
424
+ m = self ._match (regex , t )
425
+ if m is not None :
426
+ matches .append (m )
427
+ else :
428
+ break
429
+
430
+ if not matches :
431
+ self .logger .debug (
432
+ "%s: no expected match[%s] found,\n search starting from %s (around %s), "
433
+ "where first line seen as:\n %s"
434
+ "and ending at %s (around %s), where last line seen as:\n %s" ,
435
+ self ,
436
+ regex .pattern ,
437
+ * self ._debug_info_s ,
438
+ * self ._debug_info_e ,
439
+ )
440
+ if timeout and raise_on_timeout :
441
+ raise timing .TimeoutException (
442
+ "No match[%s] found in %.2fs." , regex .pattern , timeout
350
443
)
351
- timeout = end_time - time .time ()
352
- except timing .TimeoutException :
353
- if not matches and raise_on_timeout :
354
- raise
355
444
356
445
return matches
357
446
0 commit comments