| | """pytest configuration |
| | |
| | Extends output capture as needed by pybind11: ignore constructors, optional unordered lines. |
| | Adds docstring and exceptions message sanitizers. |
| | """ |
| |
|
| | import contextlib |
| | import difflib |
| | import gc |
| | import multiprocessing |
| | import re |
| | import sys |
| | import textwrap |
| | import traceback |
| |
|
| | import pytest |
| |
|
| | |
| | try: |
| | import pybind11_tests |
| | except Exception: |
| | |
| | traceback.print_exc() |
| | raise |
| |
|
| |
|
| | @pytest.fixture(scope="session", autouse=True) |
| | def use_multiprocessing_forkserver_on_linux(): |
| | if sys.platform != "linux": |
| | |
| | return |
| |
|
| | |
| | |
| | |
| | |
| | |
| | multiprocessing.set_start_method("forkserver") |
| |
|
| |
|
| | _long_marker = re.compile(r"([0-9])L") |
| | _hexadecimal = re.compile(r"0x[0-9a-fA-F]+") |
| |
|
| | |
| | collect_ignore = [] |
| |
|
| |
|
| | def _strip_and_dedent(s): |
| | """For triple-quote strings""" |
| | return textwrap.dedent(s.lstrip("\n").rstrip()) |
| |
|
| |
|
| | def _split_and_sort(s): |
| | """For output which does not require specific line order""" |
| | return sorted(_strip_and_dedent(s).splitlines()) |
| |
|
| |
|
| | def _make_explanation(a, b): |
| | """Explanation for a failed assert -- the a and b arguments are List[str]""" |
| | return ["--- actual / +++ expected"] + [ |
| | line.strip("\n") for line in difflib.ndiff(a, b) |
| | ] |
| |
|
| |
|
| | class Output: |
| | """Basic output post-processing and comparison""" |
| |
|
| | def __init__(self, string): |
| | self.string = string |
| | self.explanation = [] |
| |
|
| | def __str__(self): |
| | return self.string |
| |
|
| | def __eq__(self, other): |
| | |
| | a = [ |
| | line |
| | for line in self.string.strip().splitlines() |
| | if not line.startswith("###") |
| | ] |
| | b = _strip_and_dedent(other).splitlines() |
| | if a == b: |
| | return True |
| | self.explanation = _make_explanation(a, b) |
| | return False |
| |
|
| |
|
| | class Unordered(Output): |
| | """Custom comparison for output without strict line ordering""" |
| |
|
| | def __eq__(self, other): |
| | a = _split_and_sort(self.string) |
| | b = _split_and_sort(other) |
| | if a == b: |
| | return True |
| | self.explanation = _make_explanation(a, b) |
| | return False |
| |
|
| |
|
| | class Capture: |
| | def __init__(self, capfd): |
| | self.capfd = capfd |
| | self.out = "" |
| | self.err = "" |
| |
|
| | def __enter__(self): |
| | self.capfd.readouterr() |
| | return self |
| |
|
| | def __exit__(self, *args): |
| | self.out, self.err = self.capfd.readouterr() |
| |
|
| | def __eq__(self, other): |
| | a = Output(self.out) |
| | b = other |
| | if a == b: |
| | return True |
| | self.explanation = a.explanation |
| | return False |
| |
|
| | def __str__(self): |
| | return self.out |
| |
|
| | def __contains__(self, item): |
| | return item in self.out |
| |
|
| | @property |
| | def unordered(self): |
| | return Unordered(self.out) |
| |
|
| | @property |
| | def stderr(self): |
| | return Output(self.err) |
| |
|
| |
|
| | @pytest.fixture() |
| | def capture(capsys): |
| | """Extended `capsys` with context manager and custom equality operators""" |
| | return Capture(capsys) |
| |
|
| |
|
| | class SanitizedString: |
| | def __init__(self, sanitizer): |
| | self.sanitizer = sanitizer |
| | self.string = "" |
| | self.explanation = [] |
| |
|
| | def __call__(self, thing): |
| | self.string = self.sanitizer(thing) |
| | return self |
| |
|
| | def __eq__(self, other): |
| | a = self.string |
| | b = _strip_and_dedent(other) |
| | if a == b: |
| | return True |
| | self.explanation = _make_explanation(a.splitlines(), b.splitlines()) |
| | return False |
| |
|
| |
|
| | def _sanitize_general(s): |
| | s = s.strip() |
| | s = s.replace("pybind11_tests.", "m.") |
| | return _long_marker.sub(r"\1", s) |
| |
|
| |
|
| | def _sanitize_docstring(thing): |
| | s = thing.__doc__ |
| | return _sanitize_general(s) |
| |
|
| |
|
| | @pytest.fixture() |
| | def doc(): |
| | """Sanitize docstrings and add custom failure explanation""" |
| | return SanitizedString(_sanitize_docstring) |
| |
|
| |
|
| | def _sanitize_message(thing): |
| | s = str(thing) |
| | s = _sanitize_general(s) |
| | return _hexadecimal.sub("0", s) |
| |
|
| |
|
| | @pytest.fixture() |
| | def msg(): |
| | """Sanitize messages and add custom failure explanation""" |
| | return SanitizedString(_sanitize_message) |
| |
|
| |
|
| | def pytest_assertrepr_compare(op, left, right): |
| | """Hook to insert custom failure explanation""" |
| | if hasattr(left, "explanation"): |
| | return left.explanation |
| | return None |
| |
|
| |
|
| | def gc_collect(): |
| | """Run the garbage collector twice (needed when running |
| | reference counting tests with PyPy)""" |
| | gc.collect() |
| | gc.collect() |
| |
|
| |
|
| | def pytest_configure(): |
| | pytest.suppress = contextlib.suppress |
| | pytest.gc_collect = gc_collect |
| |
|
| |
|
| | def pytest_report_header(config): |
| | del config |
| | assert ( |
| | pybind11_tests.compiler_info is not None |
| | ), "Please update pybind11_tests.cpp if this assert fails." |
| | return ( |
| | "C++ Info:" |
| | f" {pybind11_tests.compiler_info}" |
| | f" {pybind11_tests.cpp_std}" |
| | f" {pybind11_tests.PYBIND11_INTERNALS_ID}" |
| | f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" |
| | ) |
| |
|