Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:51

0001 """pytest configuration
0002 
0003 Extends output capture as needed by pybind11: ignore constructors, optional unordered lines.
0004 Adds docstring and exceptions message sanitizers.
0005 """
0006 
0007 import contextlib
0008 import difflib
0009 import gc
0010 import multiprocessing
0011 import os
0012 import re
0013 import textwrap
0014 
0015 import pytest
0016 
0017 # Early diagnostic for failed imports
0018 import pybind11_tests
0019 
0020 
0021 @pytest.fixture(scope="session", autouse=True)
0022 def always_forkserver_on_unix():
0023     if os.name == "nt":
0024         return
0025 
0026     # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592
0027     # In a nutshell: fork() after starting threads == flakiness in the form of deadlocks.
0028     # It is actually a well-known pitfall, unfortunately without guard rails.
0029     # "forkserver" is more performant than "spawn" (~9s vs ~13s for tests/test_gil_scoped.py,
0030     # visit the issuecomment link above for details).
0031     # Windows does not have fork() and the associated pitfall, therefore it is best left
0032     # running with defaults.
0033     multiprocessing.set_start_method("forkserver")
0034 
0035 
0036 _long_marker = re.compile(r"([0-9])L")
0037 _hexadecimal = re.compile(r"0x[0-9a-fA-F]+")
0038 
0039 # Avoid collecting Python3 only files
0040 collect_ignore = []
0041 
0042 
0043 def _strip_and_dedent(s):
0044     """For triple-quote strings"""
0045     return textwrap.dedent(s.lstrip("\n").rstrip())
0046 
0047 
0048 def _split_and_sort(s):
0049     """For output which does not require specific line order"""
0050     return sorted(_strip_and_dedent(s).splitlines())
0051 
0052 
0053 def _make_explanation(a, b):
0054     """Explanation for a failed assert -- the a and b arguments are List[str]"""
0055     return ["--- actual / +++ expected"] + [
0056         line.strip("\n") for line in difflib.ndiff(a, b)
0057     ]
0058 
0059 
0060 class Output:
0061     """Basic output post-processing and comparison"""
0062 
0063     def __init__(self, string):
0064         self.string = string
0065         self.explanation = []
0066 
0067     def __str__(self):
0068         return self.string
0069 
0070     def __eq__(self, other):
0071         # Ignore constructor/destructor output which is prefixed with "###"
0072         a = [
0073             line
0074             for line in self.string.strip().splitlines()
0075             if not line.startswith("###")
0076         ]
0077         b = _strip_and_dedent(other).splitlines()
0078         if a == b:
0079             return True
0080         else:
0081             self.explanation = _make_explanation(a, b)
0082             return False
0083 
0084 
0085 class Unordered(Output):
0086     """Custom comparison for output without strict line ordering"""
0087 
0088     def __eq__(self, other):
0089         a = _split_and_sort(self.string)
0090         b = _split_and_sort(other)
0091         if a == b:
0092             return True
0093         else:
0094             self.explanation = _make_explanation(a, b)
0095             return False
0096 
0097 
0098 class Capture:
0099     def __init__(self, capfd):
0100         self.capfd = capfd
0101         self.out = ""
0102         self.err = ""
0103 
0104     def __enter__(self):
0105         self.capfd.readouterr()
0106         return self
0107 
0108     def __exit__(self, *args):
0109         self.out, self.err = self.capfd.readouterr()
0110 
0111     def __eq__(self, other):
0112         a = Output(self.out)
0113         b = other
0114         if a == b:
0115             return True
0116         else:
0117             self.explanation = a.explanation
0118             return False
0119 
0120     def __str__(self):
0121         return self.out
0122 
0123     def __contains__(self, item):
0124         return item in self.out
0125 
0126     @property
0127     def unordered(self):
0128         return Unordered(self.out)
0129 
0130     @property
0131     def stderr(self):
0132         return Output(self.err)
0133 
0134 
0135 @pytest.fixture
0136 def capture(capsys):
0137     """Extended `capsys` with context manager and custom equality operators"""
0138     return Capture(capsys)
0139 
0140 
0141 class SanitizedString:
0142     def __init__(self, sanitizer):
0143         self.sanitizer = sanitizer
0144         self.string = ""
0145         self.explanation = []
0146 
0147     def __call__(self, thing):
0148         self.string = self.sanitizer(thing)
0149         return self
0150 
0151     def __eq__(self, other):
0152         a = self.string
0153         b = _strip_and_dedent(other)
0154         if a == b:
0155             return True
0156         else:
0157             self.explanation = _make_explanation(a.splitlines(), b.splitlines())
0158             return False
0159 
0160 
0161 def _sanitize_general(s):
0162     s = s.strip()
0163     s = s.replace("pybind11_tests.", "m.")
0164     s = _long_marker.sub(r"\1", s)
0165     return s
0166 
0167 
0168 def _sanitize_docstring(thing):
0169     s = thing.__doc__
0170     s = _sanitize_general(s)
0171     return s
0172 
0173 
0174 @pytest.fixture
0175 def doc():
0176     """Sanitize docstrings and add custom failure explanation"""
0177     return SanitizedString(_sanitize_docstring)
0178 
0179 
0180 def _sanitize_message(thing):
0181     s = str(thing)
0182     s = _sanitize_general(s)
0183     s = _hexadecimal.sub("0", s)
0184     return s
0185 
0186 
0187 @pytest.fixture
0188 def msg():
0189     """Sanitize messages and add custom failure explanation"""
0190     return SanitizedString(_sanitize_message)
0191 
0192 
0193 # noinspection PyUnusedLocal
0194 def pytest_assertrepr_compare(op, left, right):
0195     """Hook to insert custom failure explanation"""
0196     if hasattr(left, "explanation"):
0197         return left.explanation
0198 
0199 
0200 @contextlib.contextmanager
0201 def suppress(exception):
0202     """Suppress the desired exception"""
0203     try:
0204         yield
0205     except exception:
0206         pass
0207 
0208 
0209 def gc_collect():
0210     """Run the garbage collector twice (needed when running
0211     reference counting tests with PyPy)"""
0212     gc.collect()
0213     gc.collect()
0214 
0215 
0216 def pytest_configure():
0217     pytest.suppress = suppress
0218     pytest.gc_collect = gc_collect
0219 
0220 
0221 def pytest_report_header(config):
0222     del config  # Unused.
0223     assert (
0224         pybind11_tests.compiler_info is not None
0225     ), "Please update pybind11_tests.cpp if this assert fails."
0226     return (
0227         "C++ Info:"
0228         f" {pybind11_tests.compiler_info}"
0229         f" {pybind11_tests.cpp_std}"
0230         f" {pybind11_tests.PYBIND11_INTERNALS_ID}"
0231         f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}"
0232     )