@@ -413,8 +413,8 @@ class RunResult:
413
413
def __init__ (
414
414
self ,
415
415
ret : Union [int , ExitCode ],
416
- outlines : Sequence [str ],
417
- errlines : Sequence [str ],
416
+ outlines : List [str ],
417
+ errlines : List [str ],
418
418
duration : float ,
419
419
) -> None :
420
420
try :
@@ -1318,49 +1318,32 @@ class LineMatcher:
1318
1318
1319
1319
The constructor takes a list of lines without their trailing newlines, i.e.
1320
1320
``text.splitlines()``.
1321
-
1322
1321
"""
1323
1322
1324
- def __init__ (self , lines ) :
1323
+ def __init__ (self , lines : List [ str ]) -> None :
1325
1324
self .lines = lines
1326
- self ._log_output = []
1325
+ self ._log_output = [] # type: List[str]
1327
1326
1328
- def str (self ):
1329
- """Return the entire original text."""
1330
- return "\n " .join (self .lines )
1331
-
1332
- def _getlines (self , lines2 ):
1327
+ def _getlines (self , lines2 : Union [str , Sequence [str ], Source ]) -> Sequence [str ]:
1333
1328
if isinstance (lines2 , str ):
1334
1329
lines2 = Source (lines2 )
1335
1330
if isinstance (lines2 , Source ):
1336
1331
lines2 = lines2 .strip ().lines
1337
1332
return lines2
1338
1333
1339
- def fnmatch_lines_random (self , lines2 ):
1340
- """Check lines exist in the output using in any order.
1341
-
1342
- Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
1343
- lines which have to occur in the output, in any order.
1344
-
1334
+ def fnmatch_lines_random (self , lines2 : Sequence [str ]) -> None :
1335
+ """Check lines exist in the output in any order (using :func:`python:fnmatch.fnmatch`).
1345
1336
"""
1346
1337
self ._match_lines_random (lines2 , fnmatch )
1347
1338
1348
- def re_match_lines_random (self , lines2 ):
1349
- """Check lines exist in the output using ``re.match``, in any order.
1350
-
1351
- The argument is a list of lines which have to occur in the output, in
1352
- any order.
1353
-
1339
+ def re_match_lines_random (self , lines2 : Sequence [str ]) -> None :
1340
+ """Check lines exist in the output in any order (using :func:`python:re.match`).
1354
1341
"""
1355
- self ._match_lines_random (lines2 , lambda name , pat : re .match (pat , name ))
1356
-
1357
- def _match_lines_random (self , lines2 , match_func ):
1358
- """Check lines exist in the output.
1359
-
1360
- The argument is a list of lines which have to occur in the output, in
1361
- any order. Each line can contain glob whildcards.
1342
+ self ._match_lines_random (lines2 , lambda name , pat : bool (re .match (pat , name )))
1362
1343
1363
- """
1344
+ def _match_lines_random (
1345
+ self , lines2 : Sequence [str ], match_func : Callable [[str , str ], bool ]
1346
+ ) -> None :
1364
1347
lines2 = self ._getlines (lines2 )
1365
1348
for line in lines2 :
1366
1349
for x in self .lines :
@@ -1371,46 +1354,67 @@ def _match_lines_random(self, lines2, match_func):
1371
1354
self ._log ("line %r not found in output" % line )
1372
1355
raise ValueError (self ._log_text )
1373
1356
1374
- def get_lines_after (self , fnline ) :
1357
+ def get_lines_after (self , fnline : str ) -> Sequence [ str ] :
1375
1358
"""Return all lines following the given line in the text.
1376
1359
1377
1360
The given line can contain glob wildcards.
1378
-
1379
1361
"""
1380
1362
for i , line in enumerate (self .lines ):
1381
1363
if fnline == line or fnmatch (line , fnline ):
1382
1364
return self .lines [i + 1 :]
1383
1365
raise ValueError ("line %r not found in output" % fnline )
1384
1366
1385
- def _log (self , * args ):
1367
+ def _log (self , * args ) -> None :
1386
1368
self ._log_output .append (" " .join (str (x ) for x in args ))
1387
1369
1388
1370
@property
1389
- def _log_text (self ):
1371
+ def _log_text (self ) -> str :
1390
1372
return "\n " .join (self ._log_output )
1391
1373
1392
- def fnmatch_lines (self , lines2 ):
1393
- """Search captured text for matching lines using ``fnmatch.fnmatch``.
1374
+ def fnmatch_lines (
1375
+ self , lines2 : Sequence [str ], * , consecutive : bool = False
1376
+ ) -> None :
1377
+ """Check lines exist in the output (using :func:`python:fnmatch.fnmatch`).
1394
1378
1395
1379
The argument is a list of lines which have to match and can use glob
1396
1380
wildcards. If they do not match a pytest.fail() is called. The
1397
1381
matches and non-matches are also shown as part of the error message.
1382
+
1383
+ :param lines2: string patterns to match.
1384
+ :param consecutive: match lines consecutive?
1398
1385
"""
1399
1386
__tracebackhide__ = True
1400
- self ._match_lines (lines2 , fnmatch , "fnmatch" )
1387
+ self ._match_lines (lines2 , fnmatch , "fnmatch" , consecutive = consecutive )
1401
1388
1402
- def re_match_lines (self , lines2 ):
1403
- """Search captured text for matching lines using ``re.match``.
1389
+ def re_match_lines (
1390
+ self , lines2 : Sequence [str ], * , consecutive : bool = False
1391
+ ) -> None :
1392
+ """Check lines exist in the output (using :func:`python:re.match`).
1404
1393
1405
1394
The argument is a list of lines which have to match using ``re.match``.
1406
1395
If they do not match a pytest.fail() is called.
1407
1396
1408
1397
The matches and non-matches are also shown as part of the error message.
1398
+
1399
+ :param lines2: string patterns to match.
1400
+ :param consecutive: match lines consecutively?
1409
1401
"""
1410
1402
__tracebackhide__ = True
1411
- self ._match_lines (lines2 , lambda name , pat : re .match (pat , name ), "re.match" )
1403
+ self ._match_lines (
1404
+ lines2 ,
1405
+ lambda name , pat : bool (re .match (pat , name )),
1406
+ "re.match" ,
1407
+ consecutive = consecutive ,
1408
+ )
1412
1409
1413
- def _match_lines (self , lines2 , match_func , match_nickname ):
1410
+ def _match_lines (
1411
+ self ,
1412
+ lines2 : Sequence [str ],
1413
+ match_func : Callable [[str , str ], bool ],
1414
+ match_nickname : str ,
1415
+ * ,
1416
+ consecutive : bool = False
1417
+ ) -> None :
1414
1418
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
1415
1419
1416
1420
:param list[str] lines2: list of string patterns to match. The actual
@@ -1420,28 +1424,40 @@ def _match_lines(self, lines2, match_func, match_nickname):
1420
1424
pattern
1421
1425
:param str match_nickname: the nickname for the match function that
1422
1426
will be logged to stdout when a match occurs
1427
+ :param consecutive: match lines consecutively?
1423
1428
"""
1424
- assert isinstance (lines2 , collections .abc .Sequence )
1429
+ if not isinstance (lines2 , collections .abc .Sequence ):
1430
+ raise TypeError ("invalid type for lines2: {}" .format (type (lines2 ).__name__ ))
1425
1431
lines2 = self ._getlines (lines2 )
1426
1432
lines1 = self .lines [:]
1427
1433
nextline = None
1428
1434
extralines = []
1429
1435
__tracebackhide__ = True
1430
1436
wnick = len (match_nickname ) + 1
1437
+ started = False
1431
1438
for line in lines2 :
1432
1439
nomatchprinted = False
1433
1440
while lines1 :
1434
1441
nextline = lines1 .pop (0 )
1435
1442
if line == nextline :
1436
1443
self ._log ("exact match:" , repr (line ))
1444
+ started = True
1437
1445
break
1438
1446
elif match_func (nextline , line ):
1439
1447
self ._log ("%s:" % match_nickname , repr (line ))
1440
1448
self ._log (
1441
1449
"{:>{width}}" .format ("with:" , width = wnick ), repr (nextline )
1442
1450
)
1451
+ started = True
1443
1452
break
1444
1453
else :
1454
+ if consecutive and started :
1455
+ msg = "no consecutive match: {!r}" .format (line )
1456
+ self ._log (msg )
1457
+ self ._log (
1458
+ "{:>{width}}" .format ("with:" , width = wnick ), repr (nextline )
1459
+ )
1460
+ self ._fail (msg )
1445
1461
if not nomatchprinted :
1446
1462
self ._log (
1447
1463
"{:>{width}}" .format ("nomatch:" , width = wnick ), repr (line )
@@ -1455,23 +1471,27 @@ def _match_lines(self, lines2, match_func, match_nickname):
1455
1471
self ._fail (msg )
1456
1472
self ._log_output = []
1457
1473
1458
- def no_fnmatch_line (self , pat ) :
1474
+ def no_fnmatch_line (self , pat : str ) -> None :
1459
1475
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
1460
1476
1461
1477
:param str pat: the pattern to match lines.
1462
1478
"""
1463
1479
__tracebackhide__ = True
1464
1480
self ._no_match_line (pat , fnmatch , "fnmatch" )
1465
1481
1466
- def no_re_match_line (self , pat ) :
1482
+ def no_re_match_line (self , pat : str ) -> None :
1467
1483
"""Ensure captured lines do not match the given pattern, using ``re.match``.
1468
1484
1469
1485
:param str pat: the regular expression to match lines.
1470
1486
"""
1471
1487
__tracebackhide__ = True
1472
- self ._no_match_line (pat , lambda name , pat : re .match (pat , name ), "re.match" )
1488
+ self ._no_match_line (
1489
+ pat , lambda name , pat : bool (re .match (pat , name )), "re.match"
1490
+ )
1473
1491
1474
- def _no_match_line (self , pat , match_func , match_nickname ):
1492
+ def _no_match_line (
1493
+ self , pat : str , match_func : Callable [[str , str ], bool ], match_nickname : str
1494
+ ) -> None :
1475
1495
"""Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``
1476
1496
1477
1497
:param str pat: the pattern to match lines
@@ -1492,8 +1512,12 @@ def _no_match_line(self, pat, match_func, match_nickname):
1492
1512
self ._log ("{:>{width}}" .format ("and:" , width = wnick ), repr (line ))
1493
1513
self ._log_output = []
1494
1514
1495
- def _fail (self , msg ) :
1515
+ def _fail (self , msg : str ) -> None :
1496
1516
__tracebackhide__ = True
1497
1517
log_text = self ._log_text
1498
1518
self ._log_output = []
1499
1519
pytest .fail (log_text )
1520
+
1521
+ def str (self ) -> str :
1522
+ """Return the entire original text."""
1523
+ return "\n " .join (self .lines )
0 commit comments