13
13
from _pytest .compat import safe_getattr
14
14
from _pytest .fixtures import FixtureRequest
15
15
from _pytest .outcomes import Skipped
16
+ from _pytest .python_api import approx
16
17
from _pytest .warning_types import PytestWarning
17
18
18
19
DOCTEST_REPORT_CHOICE_NONE = "none"
@@ -286,6 +287,7 @@ def _get_flag_lookup():
286
287
COMPARISON_FLAGS = doctest .COMPARISON_FLAGS ,
287
288
ALLOW_UNICODE = _get_allow_unicode_flag (),
288
289
ALLOW_BYTES = _get_allow_bytes_flag (),
290
+ NUMBER = _get_number_flag (),
289
291
)
290
292
291
293
@@ -453,10 +455,15 @@ def func():
453
455
454
456
def _get_checker ():
455
457
"""
456
- Returns a doctest.OutputChecker subclass that takes in account the
457
- ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
458
- to strip b'' prefixes.
459
- Useful when the same doctest should run in Python 2 and Python 3.
458
+ Returns a doctest.OutputChecker subclass that supports some
459
+ additional options:
460
+
461
+ * ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
462
+ prefixes (respectively) in string literals. Useful when the same
463
+ doctest should run in Python 2 and Python 3.
464
+
465
+ * NUMBER to ignore floating-point differences smaller than the
466
+ precision of the literal number in the doctest.
460
467
461
468
An inner class is used to avoid importing "doctest" at the module
462
469
level.
@@ -469,38 +476,89 @@ def _get_checker():
469
476
470
477
class LiteralsOutputChecker (doctest .OutputChecker ):
471
478
"""
472
- Copied from doctest_nose_plugin.py from the nltk project:
473
- https://github.com/nltk/nltk
474
-
475
- Further extended to also support byte literals.
479
+ Based on doctest_nose_plugin.py from the nltk project
480
+ (https://github.com/nltk/nltk) and on the "numtest" doctest extension
481
+ by Sebastien Boisgerault (https://github.com/boisgera/numtest).
476
482
"""
477
483
478
484
_unicode_literal_re = re .compile (r"(\W|^)[uU]([rR]?[\'\"])" , re .UNICODE )
479
485
_bytes_literal_re = re .compile (r"(\W|^)[bB]([rR]?[\'\"])" , re .UNICODE )
486
+ _number_re = re .compile (
487
+ r"""
488
+ (?P<number>
489
+ (?P<mantissa>
490
+ (?P<integer1> [+-]?\d*)\.(?P<fraction>\d+)
491
+ |
492
+ (?P<integer2> [+-]?\d+)\.
493
+ )
494
+ (?:
495
+ [Ee]
496
+ (?P<exponent1> [+-]?\d+)
497
+ )?
498
+ |
499
+ (?P<integer3> [+-]?\d+)
500
+ (?:
501
+ [Ee]
502
+ (?P<exponent2> [+-]?\d+)
503
+ )
504
+ )
505
+ """ ,
506
+ re .VERBOSE ,
507
+ )
480
508
481
509
def check_output (self , want , got , optionflags ):
482
- res = doctest .OutputChecker .check_output (self , want , got , optionflags )
483
- if res :
510
+ if doctest .OutputChecker .check_output (self , want , got , optionflags ):
484
511
return True
485
512
486
513
allow_unicode = optionflags & _get_allow_unicode_flag ()
487
514
allow_bytes = optionflags & _get_allow_bytes_flag ()
488
- if not allow_unicode and not allow_bytes :
489
- return False
515
+ allow_number = optionflags & _get_number_flag ()
490
516
491
- else : # pragma: no cover
492
-
493
- def remove_prefixes (regex , txt ):
494
- return re .sub (regex , r"\1\2" , txt )
517
+ if not allow_unicode and not allow_bytes and not allow_number :
518
+ return False
495
519
496
- if allow_unicode :
497
- want = remove_prefixes (self ._unicode_literal_re , want )
498
- got = remove_prefixes (self ._unicode_literal_re , got )
499
- if allow_bytes :
500
- want = remove_prefixes (self ._bytes_literal_re , want )
501
- got = remove_prefixes (self ._bytes_literal_re , got )
502
- res = doctest .OutputChecker .check_output (self , want , got , optionflags )
503
- return res
520
+ def remove_prefixes (regex , txt ):
521
+ return re .sub (regex , r"\1\2" , txt )
522
+
523
+ if allow_unicode :
524
+ want = remove_prefixes (self ._unicode_literal_re , want )
525
+ got = remove_prefixes (self ._unicode_literal_re , got )
526
+
527
+ if allow_bytes :
528
+ want = remove_prefixes (self ._bytes_literal_re , want )
529
+ got = remove_prefixes (self ._bytes_literal_re , got )
530
+
531
+ if allow_number :
532
+ got = self ._remove_unwanted_precision (want , got )
533
+
534
+ return doctest .OutputChecker .check_output (self , want , got , optionflags )
535
+
536
+ def _remove_unwanted_precision (self , want , got ):
537
+ wants = list (self ._number_re .finditer (want ))
538
+ gots = list (self ._number_re .finditer (got ))
539
+ if len (wants ) != len (gots ):
540
+ return got
541
+ offset = 0
542
+ for w , g in zip (wants , gots ):
543
+ fraction = w .group ("fraction" )
544
+ exponent = w .group ("exponent1" )
545
+ if exponent is None :
546
+ exponent = w .group ("exponent2" )
547
+ if fraction is None :
548
+ precision = 0
549
+ else :
550
+ precision = len (fraction )
551
+ if exponent is not None :
552
+ precision -= int (exponent )
553
+ if float (w .group ()) == approx (float (g .group ()), abs = 10 ** - precision ):
554
+ # They're close enough. Replace the text we actually
555
+ # got with the text we want, so that it will match when we
556
+ # check the string literally.
557
+ got = (
558
+ got [: g .start () + offset ] + w .group () + got [g .end () + offset :]
559
+ )
560
+ offset += w .end () - w .start () - (g .end () - g .start ())
561
+ return got
504
562
505
563
_get_checker .LiteralsOutputChecker = LiteralsOutputChecker
506
564
return _get_checker .LiteralsOutputChecker ()
@@ -524,6 +582,15 @@ def _get_allow_bytes_flag():
524
582
return doctest .register_optionflag ("ALLOW_BYTES" )
525
583
526
584
585
+ def _get_number_flag ():
586
+ """
587
+ Registers and returns the NUMBER flag.
588
+ """
589
+ import doctest
590
+
591
+ return doctest .register_optionflag ("NUMBER" )
592
+
593
+
527
594
def _get_report_choice (key ):
528
595
"""
529
596
This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
0 commit comments